{
  "markdown": "# PageSpeed Insights MCP\n\nMeasures a web page with Google PageSpeed Insights and reports the median of\nseveral runs with the spread beside it, so a number arrives with its\nuncertainty. Works from an assistant that speaks MCP, or from a terminal.\n\nStandard library only. No virtual environment, no `pip`, no compiled\ndependency. Clone it and run it.\n\n[Docs](https://considus.com/pagespeed-insights-mcp/) · [Install](https://considus.com/pagespeed-insights-mcp/install/) · [FAQ](https://considus.com/pagespeed-insights-mcp/faq/)\n\n[![pagespeed-insights-mcp MCP server](https://glama.ai/mcp/servers/Considus/pagespeed-insights-mcp/badges/card.svg)](https://glama.ai/mcp/servers/Considus/pagespeed-insights-mcp)\n\n## Why another one\n\nThere are already several PageSpeed MCP servers. As far as we can tell, every\none of them only runs Lighthouse once and hands the model the number.\n\nA single Lighthouse run is noise. Total Blocking Time routinely swings\nthreefold between runs on a page that has not changed, and the headline\nperformance score is a weighted blend that inherits every bit of that. Ask once\nand you get a figure with no error bars, and no way to tell a real regression\nfrom the instrument moving. That is not a small imprecision, it is the\ndifference between measuring something and guessing at it confidently.\n\nThere is a second problem underneath, and it is worse because it is invisible.\nPageSpeed caches its result per URL and replays it. Ask five times and you can\nbe handed one analysis five times, which quietly turns a median into a vote for\nwhatever Google happened to have cached. Minutes after a deploy on 30 July 2026\nthat is exactly what happened here, 2 of 3 runs came back with a byte-identical\ntimestamp from *before* the deploy, dragging the average back to the pre-fix\nscore while the one fresh run showed the fix working. A tool that reports one\nrun would have reported the stale number as current, with nothing to indicate\nanything was wrong.\n\nThis one takes the median of 5 runs by default, prints the min-max spread next\nto every figure, drops runs that were cached replays, and tells you how many it\ndropped.\n\n## The lab is not your users\n\nBoth of these are true, measured on the same page on the same afternoon.\n\n| | LCP | Verdict |\n|---|---|---|\n| Lighthouse lab, mobile | 21.36 s | performance **27 / 100** |\n| Real Chrome users, 28-day p75 | 1.20 s | **FAST** |\n\nThat is the BBC home page. The lab says the site is catastrophically slow. Real\npeople are getting it in 1.2 seconds.\n\nNeither number is wrong, they answer different questions. Lighthouse is a\nsimulation on throttled hardware, useful because it is repeatable and you can\nrun it against a page nobody has visited yet. Field data is what actually\nhappened to real Chrome users, and it is the only thing Google ranks on. A tool\nthat shows you one and not the other is misleading you, whichever one it picks.\n\nSo this reports both, separately, and never merges them into a single figure.\n\n## What it does\n\n- Median of N **distinct** analyses, with the min-max spread beside every score\n  and metric.\n- Keeps asking until it has N genuinely different analyses, because Google\n  re-analyses a URL about once a minute and replays the cached result to\n  everything that asks in between. Five requests in a row is one measurement\n  five times over.\n- Says what that cost in calls and seconds, and reports fewer analyses honestly\n  rather than padding the count when time runs out.\n- Real-user data from the Chrome UX Report, current and up to 6 months of\n  weekly history, when Google has it.\n- Says plainly when Google has none, rather than letting a lab score stand in\n  for evidence about real visitors.\n- Tells an exhausted quota apart from a broken page, and a misconfigured key\n  apart from both.\n\n## Things to actually ask it\n\nFour that exercise different parts of it, and none need you to know a tool name.\n\n**\"How fast is considus.com on mobile, and how confident are you in that\nnumber?\"** The whole reason this exists. You get the median of several genuinely\ndistinct analyses and the spread around it, not one run quoted as fact.\n\n**\"Is there real user data for this site, or only lab numbers?\"** Reads the\nChrome UX Report, which is the only evidence here about actual visitors and the\nonly thing Google ranks on. Plenty of small sites have none, and it says so\nrather than letting a lab score stand in.\n\n**\"What's actually holding up the LCP on that page?\"** Splits one number into\nthe four phases it is made of, so you find out whether it is the server, the\ndiscovery, the download or the paint. Fast, one call, no averaging needed.\n\n**\"Did that change help?\"** Records a baseline on the first call, then compares\nafter you have made the change. It only calls a difference real when the two\nranges do not overlap, which is what stops you celebrating the instrument\nmoving.\n\n## Before you start\n\nYou need an API key from Google Cloud. It is free, it takes about a minute, and\nthere is one decision to make, at step 3.\n\n1. Open [console.cloud.google.com](https://console.cloud.google.com/) and pick\n   your account.\n2. **APIs & Services**.\n3. **API Library** in the sidebar. Search for **PageSpeed Insights API**,\n   select it, press **Enable**.\n   > **Decide here, while you are on this screen.** If you also want real-user\n   > data, meaning what actual Chrome visitors experienced and how it has moved\n   > over the last six months, search for **Chrome UX Report API** and enable\n   > that as well before moving on. Same key, no second credential, one more\n   > search on the screen you are already looking at. Skip it and everything\n   > else still works, you just get lab measurements only. Adding it later means\n   > coming back to this screen and to step 7, which is the only reason it is\n   > worth deciding now.\n4. **Credentials**, **from the sidebar, not from the page you are on**.\n5. **Create credentials** at the top, then **API key**.\n6. Give it a name.\n7. Under **API restrictions**, tick **PageSpeed Insights API**, and tick\n   **Chrome UX Report API** too if you enabled it at step 3.\n8. Leave **Authenticate API calls through a service account** unticked.\n9. Leave **Application restrictions** on **None**.\n10. **Create**, then copy the key.\n\nStep 4 is the one that defeats people, and it defeated me. There are two routes\nto a credentials screen and only one of them offers an API key. Reach it from\nthe page you are already on and the button gives you OAuth clients and service\naccounts, no API key anywhere, and you reasonably conclude PageSpeed does not\nsupport them. It does. The sidebar entry is a different screen, and it sits just\nbelow API Library.\n\nStep 9 looks wrong and is not. An HTTP-referrer restriction is the safe-looking\nchoice on that form, and it makes the key unusable from a program like this\none, because there is no referring web page. The API restriction at step 7 is\nthe one that limits what the key can reach.\n\n## Install\n\nWhichever route you take, clone it somewhere permanent, a folder in your home\ndirectory is right. Your assistant's config will point at that exact path, so a\nfolder that later moves is a connection that breaks. Not Downloads, not a temp\nfolder.\n\nBoth routes finish the same way. `setup.py` opens a small page in your browser,\nserved from your own machine on a random port behind a single-use link. It\nshuts itself down when you are finished and it never writes down anything you\ntype. Paste the key in. It checks the key against Google before saving a thing,\nand tells you whether real-user data is available.\n\nRun it again any time. It notices you have set it up before, and a blank key\nfield means keep the one you have.\n\n### Have an assistant do it\n\nPaste this into an AI assistant that runs shell commands **on this computer**.\nRead what it proposes before you let it run.\n\n```\nPlease install the PageSpeed Insights MCP server from\nhttps://github.com/Considus/pagespeed-insights-mcp on this computer, following the\nInstall section of its README exactly. Clone it into a permanent folder in my home\ndirectory, then run setup.py and tell me the local link it prints so I can finish\nsetup in my browser. Show me each command before you run it.\n```\n\n### Or run the commands yourself\n\nPython 3.9 or newer, which macOS and most Linux machines already have. On\nWindows, install it from python.org or run `winget install Python.Python.3.12`.\n\n```bash\ngit clone https://github.com/Considus/pagespeed-insights-mcp.git\ncd pagespeed-insights-mcp\npython3 setup.py\n```\n\n## Connect it to your assistant\n\nWhen setup finishes it hands you a second prompt. Paste that into whichever\nassistant you want measuring your pages, Claude, Cursor, Windsurf, Zed, Codex\nCLI, VS Code Copilot, anything that speaks MCP. Restart the app afterwards, MCP\nservers load at startup.\n\nIt works this way round on purpose. Every client keeps its MCP config somewhere\ndifferent, under a different key, and those locations move. An assistant already\nknows where its own config lives, so asking it beats shipping a list of paths\nthat quietly rots. The prompt carries no key, only the name, the command and the\npath.\n\n### Tools\n\n| Tool | What it does |\n|---|---|\n| `report` | Everything in one call, and the one most people want. Scores with their spread, real-user data if Google has any, and what is failing ranked by what fixing it is worth. Returns a self-contained HTML page as well, to save or forward. Comes back as a job id. |\n| `check_pagespeed` | Scores only. Median of N distinct analyses with the spread. `urls`, `strategy` (mobile, desktop or both), `runs` (1-10, default 5). Anything past a single analysis comes back as a job id. |\n| `diagnose_page` | What is failing, ranked. Only reports a fault that failed in every analysis, because audits are as noisy as scores. Comes back as a job id. |\n| `field_data` | Real-user data from the Chrome UX Report. `urls`, and `history` for the weekly p75 series. |\n| `explain_lcp` | Which of four phases owns a slow Largest Contentful Paint: server response, the wait before the browser starts fetching the largest image, the download, then the wait before it is painted. One call, answers at once. |\n| `compare` | Did the change actually help. Measures now and compares against a saved baseline, giving a verdict only where the two ranges do not overlap. Comes back as a job id. |\n| `check_status` | Collects a measurement that was handed back as a job id. Answers immediately, every time. `job_id`, or nothing at all for the most recent one. |\n| `diagnose` | Whether the key works, whether the Chrome UX Report is reachable, and which baselines are held, without disclosing the key. |\n\n### Measurements that outlive the call\n\nEvery long check used to time out, and the server was fine the whole time. It\ncarried on measuring, finished the job properly, and posted the answer into a\npipe nobody was reading any more.\n\nThe client had given up at 60 seconds. That is a hard wall on a single tool\ncall, and progress notifications don't move it, which is worth knowing because\nthis server sends them and they look like they should. An honest measurement\ncan't fit inside it. Google re-analyses a URL about once a minute, so five\ndistinct analyses take about 150 seconds, and asking harder doesn't help, it\njust hands you the same cached analysis five times.\n\nThe work doesn't happen inside the call any more. `check_pagespeed`, `report`,\n`diagnose_page` and `compare` start the measurement, hand back a job id, and\nkeep going. Your assistant collects it with `check_status`, every 15 seconds or\nso, and `check_status` always answers straight away. When the measurement\nlands it returns exactly what the tool would have returned, so nothing\ndownstream can tell which route it took.\n\nYou don't have to do anything. The assistant polls for you.\n\nOne thing still answers on the spot, `check_pagespeed` with one URL and `runs`\nset to 1. That is a single analysis with no spread, which is the noisy one-run\nnumber this whole package exists to refuse, and the output says so in as many\nwords.\n\nA finished job sits there for an hour, in case you want to read it twice. A\nrunning one dies if the server restarts, which clients do once they have been\nidle a while, and `check_status` tells you that happened rather than leaving\nyou polling something that will never finish.\n\nIf your client waits longer than 60 seconds, tell the server and it will do\nmore inside the call. Set `inline_budget_seconds` in `settings.json`, or the\n`PAGESPEED_INLINE_BUDGET` environment variable. It can't work this out on its\nown, the timeout isn't in the protocol and the handful of environment variables\na server inherits doesn't carry it either.\n\nTwo things about `explain_lcp` are worth knowing before you read one, and both\nare printed in every answer. The four phases do **not** add up to the LCP, and\nthe gap is not rounding. Across twelve real origins the sum missed by anything\nfrom 40ms under to 2.6 seconds over. Each phase is its own 75th percentile, and\npercentiles do not add. And the phases are measured only over visits where the\nlargest element was an image, which on some sites is a small minority, so the\nanswer always says what share of visits it is describing.\n\nThat second point is the useful part as often as it is the caveat. On one large\nsite the LCP looks a comfortable 1.5 seconds, and the eighth of visits with an\nimage LCP are waiting 3.3 seconds before the image so much as starts\ndownloading. Nothing in the headline number shows that.\n\n`compare` answers the question the findings leave open, which is whether the\nchange you made did anything. The first call on a URL records a baseline and\ncompares nothing, because there is nothing to compare against yet. Make the\nchange, call it again, and it measures afresh and reports what moved.\n\nIt gives a verdict only where the two min-max ranges do not overlap at all.\nThat is deliberately conservative and it will miss small real improvements. The\nreason is that the two possible mistakes are not equally bad. Telling you\nnothing moved when something did costs you a little confidence, while telling\nyou something improved when it was noise is a claim you might repeat to a\nclient. Where a change is real it reports both the difference in medians and\nthe smaller figure the ranges actually guarantee, and the guaranteed one is the\nnumber to quote.\n\nBaselines are held in `baselines.json` beside the settings, keyed by URL and\nstrategy, and are never moved unless you ask. Field data is deliberately not\ncompared, because a 28-day rolling window cannot show a change made this week.\n\n### The skill\n\n`skills/pagespeed-insights-read/` teaches an assistant how to read the results.\nThe server refuses to state a number without its uncertainty. It cannot stop an\nassistant dropping that uncertainty on the way to an answer, and the most common\nway that goes wrong is adding four savings estimates together and promising nine\nseconds.\n\nIt is optional. Everything works without it, and the tools carry the same\nwarnings in their own descriptions. The skill is what stops those warnings being\nparaphrased away on the journey to an answer.\n\nEvery client keeps skills somewhere different, some sync them from an account\nrather than reading your disk, and some have no such thing at all. So rather than\nprinting one client's path, ask yours. Paste this in, with the folder path filled\nin from wherever you cloned this.\n\n```\nI have a skill folder on this computer and I would like you to install it into\nwhichever client you are running inside, if that client supports skills.\n\n  Skill folder = /path/to/pagespeed-insights-mcp/skills/pagespeed-insights-read\n\nPlease:\n1. Work out whether this client supports skills, and where it keeps them on this\n   machine.\n2. Install the folder there, keeping its folder name unchanged. Most clients take\n   the name of the folder as the name of the skill, so renaming it renames the\n   skill.\n3. Prefer linking to the folder above rather than copying it, if this client\n   follows links, so it updates when I next pull. Copy it if not.\n4. If this client syncs skills from an account rather than reading this disk, do\n   not guess at a path. Tell me where to add it instead.\n5. Tell me what you did and whether I need to restart the app.\n\nIf this client has no concept of skills, say so plainly and do nothing.\nEverything works without it.\n```\n\nSetup prints the same prompt with your actual path already in it.\n\nA 5-analysis check on 2 URLs takes several minutes, and asking harder will not\nspeed it up. Google re-analyses a URL roughly once a minute whatever you do, so\nthe time is spent waiting for genuinely new measurements rather than queuing\nrequests. The server sends a progress update each time a new analysis lands,\nwhich is what stops a client giving up on it. If your assistant offers to use\n`runs=1` to be quicker, the answer is no, that is the thing this exists to\nstop.\n\nStart with `diagnose` if anything looks wrong. It separates a configuration\nproblem from a slow page in about 2 seconds.\n\n## From a terminal\n\nFrom the folder you cloned into it runs as a module, `python3 -m\npagespeed_insights`. For a `pagespeed` command that works from anywhere, link\nthe launcher onto your PATH.\n\n```bash\nmkdir -p ~/.local/bin\nln -s \"$PWD/pagespeed\" ~/.local/bin/pagespeed\n```\n\nIf `~/.local/bin` is not on your PATH, add `export PATH=\"$HOME/.local/bin:$PATH\"`\nto your shell profile. On Windows there is no symlink step, add the folder to\nyour PATH or keep using `python -m pagespeed_insights` from inside it.\n\nSetup will show you that command but will not run it. It writes nothing outside\nits own settings, and a tool that quietly drops executables into a PATH\ndirectory is the thing that rule exists to prevent.\n\n```bash\npagespeed https://example.com/\npagespeed --runs 3 --strategy both https://example.com/\npagespeed --field --history https://example.com/\npagespeed --lcp https://example.com/\npagespeed --findings https://example.com/\npagespeed --compare https://example.com/\npagespeed --baselines\npagespeed --field --report report.html https://example.com/\npagespeed --json https://example.com/\n```\n\n`--report` writes one self-contained HTML page with no scripts, no network and\nnothing external. It opens from a file and survives being emailed, which matters because\nthe largest finding is often a hosting or third-party decision belonging to\nsomebody other than whoever ran the check.\n\nYour assistant can save the same page. Ask it for a report and it will ask where\nyou want the file. The server writes only to a folder you name and only when you\nask, it will not create one, and it never overwrites an existing report, because\nsomebody comparing a before against an after wants both. Name no folder and it\ngoes to a `reports` folder beside the settings, and the full path comes back so\nyou can find it.\n\nSaved files keep their embedded fonts and run about 150KB. The copy that comes\nback in conversation drops them, because 145KB of base64 is most of a context\nwindow spent on typography. Same renderer either way.\n\nWith no URL it uses whatever you saved during setup.\n\nExit codes are split by what went wrong, so something running this in CI can\ntell a broken site apart from a bad afternoon at Google.\n\n| Code | Meaning |\n|---|---|\n| 0 | fine |\n| 1 | something else went wrong |\n| 2 | bad arguments |\n| 3 | quota exhausted, infrastructure rather than the site |\n| 4 | credential rejected, configuration rather than the site |\n| 5 | page unreachable, **this one is the site** |\n| 6 | could not reach Google, network rather than the site |\n\nOnly 5 means the page is at fault. Failing a build on 3 or 6 is failing it\nbecause Google was busy.\n\n## When something goes wrong\n\nEvery failure carries the sentence that fixes it rather than a stack trace, and\nmost of them are not about your site at all. Knowing which is which saves you\nchanging something that was never broken.\n\n**A 429 and no key.** You are on the shared anonymous pool, which everyone else\nis also using and which is routinely spent by the middle of the day. It says\nnothing whatsoever about the page you were measuring. Run setup and add a key.\n\n**A 429 with a key.** Your own project quota is spent for the day. It resets at\nmidnight Pacific time.\n\n**A 400 or 403 from PageSpeed Insights.** The key is missing, wrong, or carrying\nan HTTP-referrer restriction. That last one is the usual answer, and it is step\n9 of [Before you start](#before-you-start) coming back to bite, because a\nprogram has no referring web page, so a key restricted that way is refused every\nsingle time. Use API restrictions instead, or none. While you are on that\nscreen, check the PageSpeed Insights API is enabled on the project at all.\n\n**No field data for the origin.** This is not a failure and the tool won't\nreport it as one. Google publishes real-user data only for origins with enough\ntraffic to stay anonymous, so a small, new or pre-launch site has none, and\nwon't until it has visitors. What's left is the lab, which is a simulation and\nnot evidence about anybody's real experience.\n\n**The Chrome UX Report says it has not been used in this project.** Either the\nAPI isn't enabled, or you enabled it a minute ago and it hasn't finished\npropagating. Those two are indistinguishable from out here, and this one has\nalready been retried before you see it, so wait a couple of minutes and run it\nagain before changing anything.\n\n**The Chrome UX Report refuses the key.** The key itself is fine, its API\nrestrictions just don't include Chrome UX Report API. Add it in the Google Cloud\nconsole, under Credentials, editing the key's API restrictions.\n\n**Lighthouse could not load the page.** Unlike everything above it, this one\nreally is about your site. Check the URL serves a 200 to an anonymous visitor,\nwith no login in the way and no geographic block.\n\n**It could not reach Google at all.** Network, DNS or a timeout, already retried\nwith backoff by the time it says so.\n\n**Your assistant says the request timed out.** Nothing here should run long\nenough for that any more, because anything past about a minute comes back as a\njob id instead. If you see it, something is set to do more inside the call than\nyour client will sit through, so look at `inline_budget_seconds` in\n`settings.json` and at the `PAGESPEED_INLINE_BUDGET` variable, and lower it or\ntake it out.\n\n**A job says it is lost.** The server was restarted while it was measuring,\nwhich is what a client does to a server that has been sitting idle. Start the\nmeasurement again and let the assistant keep polling while it runs.\n\nWhen you can't tell which of these you have, `diagnose` answers it. Whether the\nkey works, whether the Chrome UX Report is reachable, and which baselines are\nheld, none of it disclosing the key.\n\n## Updating\n\nThere's no package and no installer, so there's nothing to download. The server\nruns as `mcp_server.py` out of the directory you cloned into, which makes an\nupdate a pull and a restart.\n\nYour key and your saved URLs aren't in that directory, they sit in\n`settings.json` in your platform's config directory, so an update leaves them\nalone and you won't be asked for the key again.\n\nThe restart is the part that catches people out. A stdio MCP server is a\nlong-running process, and it reads `mcp_server.py` once, when the app starts it.\nChanging the file underneath a server that's already running does nothing at\nall, so quit the app properly and open it again. Closing the window isn't enough\non macOS, and neither is closing the last tab on Windows if it leaves the app in\nthe tray.\n\nReleases are tagged, and the releases page on GitHub says what changed in each\none. `pagespeed --version` tells you which one you're on, and your assistant can\nread the same number out of the server's handshake. `git pull` puts you on the\nlatest `main`, which is sometimes ahead of the newest tag.\n\n### Have an assistant do it\n\nPaste this into an AI assistant that runs shell commands **on this computer**. It\ncan do the pull, but it can't restart the app it's running inside, so the last\nstep stays yours.\n\n```\nPlease update my PageSpeed Insights MCP server. Find where it's installed by\nreading the path out of this app's MCP config rather than guessing it, run git\npull in that folder, and tell me what changed. Don't run setup.py and don't edit\nmy settings.json, my key and saved URLs are already in it. Then remind me to quit\nthis app completely and open it again, because the server only reads\nmcp_server.py at startup.\n```\n\n### Or run the commands yourself\n\nIn the folder you cloned into.\n\n```bash\ncd pagespeed-insights-mcp\ngit pull\n```\n\n## Where things live\n\nYour key and your saved URLs go in `settings.json` in your platform's config\ndirectory.\n\n- macOS, `~/Library/Application Support/pagespeed-insights-mcp/`\n- Linux, `~/.config/pagespeed-insights-mcp/`\n- Windows, `%APPDATA%\\pagespeed-insights-mcp\\`\n\nTo add, change or remove those saved URLs, run `python3 setup.py` again from the\nfolder you cloned into. The box comes back with whatever you saved last time, so\nedit the list and submit it. Empty it and you are back to naming a site every\ntime. Your key is kept unless you type a new one over it. There is no CLI flag\nfor this. The file is small and plain, so editing it by hand works just as well,\nand deleting it removes the key from the machine.\n\nOn macOS and Linux `settings.json` is written owner-readable only. On Windows it is\nnot, because Windows has no POSIX file modes and the request is quietly\nignored. There the protection comes from `%APPDATA%` living inside your user\nprofile, which is restricted to you by default. That is a real protection, but\nit is inherited rather than set by this tool, so it is worth knowing which one\nyou are relying on.\n\nRunning measurements keep their state in a `jobs` folder beside the settings,\none small file each, deleted an hour after they finish. That is what lets a poll\nfind its answer when the client has quietly restarted the server underneath it.\n\n`PAGESPEED_CONFIG_DIR` moves that wherever you like, and `PAGESPEED_API_KEY`\noverrides the stored key for anyone who would rather keep it in their own secret\nmanager. `PAGESPEED_INLINE_BUDGET` says how many seconds of work to do inside a\nsingle tool call before handing back a job id instead, and 45 is the default.\n\nThe key is a file rather than an entry in your system keychain, and that is a\ndeliberate trade worth being straight about. Reaching the keychain portably\nneeds an installed package, an installed package needs a virtual environment and\na `pip` step, and that is the entire \"clone it and run it\" advantage gone for\nthe people who need it most. Weigh that against what the credential actually is.\nA PageSpeed key, restricted as step 7 instructs, is read-only access to public\nmeasurements of public web pages. It holds no personal data, unlocks no account,\nand cannot be billed against, because the API is free. The worst a stolen one\ndoes is spend a quota of 25,000 calls a day, and you fix that by deleting the\nkey. That is a nuisance, not a breach.\n\n## What it will not do\n\nIt will not report a single run as a measurement. There is no flag for it and\nthere is not going to be.\n\nIt will not merge lab and field numbers into one figure. They disagree by an\norder of magnitude on real sites, and averaging them would destroy the only\nhonest thing here.\n\nIt will not call a change a regression when the change is inside the spread.\nThe spread is printed so you can see for yourself, and a movement inside it is\nnot evidence of anything.\n\nIt does not touch Google Search Console. Search Console needs OAuth and\nper-property authorisation, because it serves private data about a property you\nown, where PageSpeed and the Chrome UX Report serve public data about public\npages that anyone may measure. That is a different kind of tool with a different\nkind of credential, and bolting it on here would drag a consent flow into\nsomething that currently needs one string.\n\n## Tests\n\n```bash\npython3 -m unittest discover -s tests -v\n```\n\nNo API key and no network needed. CI runs them on Python 3.9 and 3.12 across\nLinux, macOS and Windows.\n\nTwo of the tests pin bugs found while building this, both of which produced a\nplausible number rather than an error. PageSpeed reports its embedded CLS\nmultiplied by 100, so a healthy 0.08 reads as 8.0 if you take it at face value,\nwhich is a catastrophic score on the one metric where those two numbers are the\nwhole story. And the Chrome UX Report returns CLS as a string while every other\nmetric is a number, so the obvious comparison raises a TypeError. Neither\nannounces itself. In a measurement tool, a wrong number that looks right is the\nonly kind of bug that matters.\n\n## Privacy Policy\n\nThis one is not local-only, and pretending otherwise would be the wrong thing to write. Measuring a page means asking Google to measure it, so the address you check goes to Google. That is the tool working rather than a leak, but it is worth stating first rather than buried.\n\n### What leaves this machine, and where it goes\n\nThe URL you are measuring, sent to the PageSpeed Insights API, and to the Chrome UX Report API when you ask for field data. Your API key travels with those requests if you have one. Nothing else. Google then loads that page from their own infrastructure and measures it, so the page owner sees a visit from Google rather than from you.\n\nGoogle's handling of those requests is Google's, governed by their privacy policy and their API terms, and it is outside anything Considus can promise for them.\n\n### What does not leave\n\nThere is no Considus server in this. No account, no telemetry, no crash reporting, no usage counting, and no licence check. We never see what you measured, when, or that you ran it at all. There is nothing on our side to see it from.\n\n### What it writes, and where\n\n`settings.json` holds your API key and any URLs you saved, in your platform's config directory, and `PAGESPEED_CONFIG_DIR` moves it wherever you like.\n\nBaselines, so a later run can tell you whether a change is real or noise, sit beside it.\n\nHTML reports go to the reports folder next to those settings, or to a folder you named in the conversation. Nothing writes to a folder that does not already exist, and nothing is ever overwritten.\n\nA running measurement writes its progress and then its result to a small file in the `jobs` folder beside the settings, because a poll and the measurement it is asking about are not always in the same server process. Those files hold measurements of public pages and nothing else, and they are deleted an hour after the job finishes.\n\nAll of it is on your own disk and all of it is yours to delete. Deleting `settings.json` removes the key from the machine.\n\n### About the key being in a file\n\nOn macOS and Linux it is written owner-readable only. On Windows that request is ignored, because Windows has no POSIX file modes, and the protection there comes from `%APPDATA%` sitting inside your user profile instead. That is real, and it is inherited rather than set by this tool, which is a weaker promise and worth knowing.\n\nIt is a file rather than a keychain entry because reaching the keychain portably needs an installed package, which needs a virtual environment and a `pip` step, which is the whole \"clone it and run it\" advantage gone. Weigh that against what the credential is. A PageSpeed key restricted as the setup instructs is read-only access to public measurements of public pages. It holds no personal data, unlocks no account, and cannot be billed against, because the API is free. The worst a stolen one does is spend a quota you can reset by deleting the key. Anyone who disagrees with that trade sets `PAGESPEED_API_KEY` from their own secret manager and no key is stored at all.\n\n### Retention, and who else gets it\n\nWe hold nothing, so there is nothing for us to keep, and nothing for us to delete on your behalf. Nothing is shared with anyone, because nothing reaches us to share. Google is the only third party involved, and only for the measurement requests above.\n\n### If you want to ask about any of this\n\nWrite to privacy@considus.com, or open an issue at [github.com/Considus/pagespeed-insights-mcp/issues](https://github.com/Considus/pagespeed-insights-mcp/issues). The policy covering considus.com itself is at [considus.com/privacy](https://considus.com/privacy/), and it is a separate document because it covers a separate thing.\n\n## Support\n\nThis is free and stays that way. Apache 2.0 means you can take it, build on it,\nand ship it commercially without owing anything back, which is deliberate.\n\nSomething broken or a number you don't believe,\n[open an issue](https://github.com/Considus/pagespeed-insights-mcp/issues).\nAnything exploitable goes through GitHub's private reporting instead, described\nin [SECURITY.md](SECURITY.md), rather than a public issue. For anything that\nfits neither, including press and licensing, it's <support@considus.com>, and\nthe other ways to reach us are at\n[considus.com/support](https://considus.com/support/).\n\nIf it stopped you chasing a regression that was never there, then consider\n[buying me a coffee](https://buymeacoffee.com/considus). If it didn't, telling\nme what it got wrong is worth more than the coffee, and a bug report about a\nnumber that looked plausible and wasn't is the most useful thing anyone can\nsend.\n\n## Licence\n\nApache 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).\n\nUnofficial community project. Not affiliated with, endorsed by, or sponsored by\nGoogle LLC. It calls two public Google APIs, bundles no Google code, and\nredistributes no Google data.\n",
  "bytes": 33668,
  "sha": "6df29d2b41c75d95a5832ddb994654df7410716db9350e267f267b2555bfbb90",
  "repo_slug": "considus/pagespeed-insights-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_com_considus_pagespeed_insights_mcp_d56690be/readme"
}