<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: md sultan mahamud</title>
    <description>The latest articles on DEV Community by md sultan mahamud (@sultan57git).</description>
    <link>https://dev.to/sultan57git</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4188609%2Fd235059f-ecf6-4bf2-9219-237aa15993df.png</url>
      <title>DEV Community: md sultan mahamud</title>
      <link>https://dev.to/sultan57git</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sultan57git"/>
    <language>en</language>
    <item>
      <title>How an MCP registry changes in a week: 5,161 new servers, 12,973 new versions, 75 removed</title>
      <dc:creator>md sultan mahamud</dc:creator>
      <pubDate>Sat, 31 Oct 2026 20:23:34 +0000</pubDate>
      <link>https://dev.to/sultan57git/how-an-mcp-registry-changes-in-a-week-5161-new-servers-12973-new-versions-75-removed-1nnj</link>
      <guid>https://dev.to/sultan57git/how-an-mcp-registry-changes-in-a-week-5161-new-servers-12973-new-versions-75-removed-1nnj</guid>
      <description>&lt;p&gt;If you build on the official MCP registry, the listing you read today is not the listing you will read next week. I run &lt;a href="https://protocolprobe.com" rel="noopener noreferrer"&gt;ProtocolProbe&lt;/a&gt;, a directory with a page for every server in that registry. Every day a job pulls the whole registry, compares it with the last snapshot it loaded, and records what changed. This post is the first eight days of that record, 2026-10-23 to 2026-10-31.&lt;/p&gt;

&lt;p&gt;Nothing here required calling a single server's tools. It is the registry's own list API, compared with itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the numbers were made
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One snapshot of the registry's latest entries per day, pulled at a different time of day (between 01:38 and 16:43 UTC). A "day" is the gap between two pulls, not midnight to midnight.&lt;/li&gt;
&lt;li&gt;Each snapshot is compared with the previous one the job loaded. That gives seven comparisons covering eight days: the run of 2026-10-25 did not load, so the comparison dated 10-26 covers two days.&lt;/li&gt;
&lt;li&gt;A server counts as &lt;strong&gt;new&lt;/strong&gt; if its name was not in the previous snapshot, &lt;strong&gt;removed&lt;/strong&gt; if it was and is gone, and as having a &lt;strong&gt;new version&lt;/strong&gt; if its latest version differs from the previous snapshot. If a server published three releases between two pulls, that is one row, not three. All version counts below are therefore "at least".&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;status change&lt;/strong&gt; is a change of &lt;code&gt;status&lt;/code&gt; on an entry whose version did not change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The week in one table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Compared with&lt;/th&gt;
&lt;th&gt;New&lt;/th&gt;
&lt;th&gt;New version&lt;/th&gt;
&lt;th&gt;Removed&lt;/th&gt;
&lt;th&gt;Status changed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10-24&lt;/td&gt;
&lt;td&gt;10-23&lt;/td&gt;
&lt;td&gt;928&lt;/td&gt;
&lt;td&gt;1,906&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-26&lt;/td&gt;
&lt;td&gt;10-24 (two days)&lt;/td&gt;
&lt;td&gt;1,014&lt;/td&gt;
&lt;td&gt;2,010&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-27&lt;/td&gt;
&lt;td&gt;10-26&lt;/td&gt;
&lt;td&gt;724&lt;/td&gt;
&lt;td&gt;1,601&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-28&lt;/td&gt;
&lt;td&gt;10-27&lt;/td&gt;
&lt;td&gt;671&lt;/td&gt;
&lt;td&gt;1,159&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-29&lt;/td&gt;
&lt;td&gt;10-28&lt;/td&gt;
&lt;td&gt;307&lt;/td&gt;
&lt;td&gt;1,258&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-30&lt;/td&gt;
&lt;td&gt;10-29&lt;/td&gt;
&lt;td&gt;962&lt;/td&gt;
&lt;td&gt;3,448&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-31&lt;/td&gt;
&lt;td&gt;10-30&lt;/td&gt;
&lt;td&gt;555&lt;/td&gt;
&lt;td&gt;1,591&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;144&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5,161&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12,973&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;207&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The arithmetic closes, and it closes on the first day too (48,551 + 928 - 34 = 49,445, the 10-24 pull). The 2026-10-23 snapshot held 48,551 latest entries. Add 5,161 new, take away 75 removed, and you get 53,637, which is exactly what the 2026-10-31 pull counted. That is +10.5% in eight days. The registry's table of every version of every server, including old ones, went from 170,076 rows to 198,499, which is +28,423 or +16.7%.&lt;/p&gt;

&lt;p&gt;No entry returned after being removed in any comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  New servers: 5,161 of them, from 3,677 publishers
&lt;/h2&gt;

&lt;p&gt;I use the part of the name before the slash as the publisher, for example &lt;code&gt;io.github.&amp;lt;account&amp;gt;&lt;/code&gt; or a reversed domain.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2,249 of the 5,161 new servers (43.6%) are under &lt;code&gt;io.github.*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The six publishers with the most new servers account for 753, which is 14.6%. The rest is a long tail: 3,677 distinct publishers for 5,161 servers.&lt;/li&gt;
&lt;li&gt;839 of the new servers (16.3%) also published a newer latest version before the window ended.&lt;/li&gt;
&lt;li&gt;14 appeared already marked deprecated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Updates: 6,630 servers, and two publishers make up 46% of the rows
&lt;/h2&gt;

&lt;p&gt;12,973 rows is not 12,973 servers. 6,630 distinct servers from 3,205 publishers had a new latest version at least once, and they did so on a different number of days:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comparisons with a new version (of 7)&lt;/th&gt;
&lt;th&gt;Servers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3,521&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1,649&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;685&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;209&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;158&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;383&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Direction: 12,958 rows went to a newer version, 2 went to an older one, and 13 could not be ordered.&lt;/p&gt;

&lt;p&gt;The long right end of the table is one publisher. 341 of the 408 servers that changed on six or seven of the seven comparisons belong to a single publisher, and two publishers together account for 6,006 of the 12,973 rows, 46.3%. Without them there are 6,967 rows across 4,320 servers. If you count owners rather than entries, the picture is a few automated release streams on top of a wide base of occasional updates.&lt;/p&gt;

&lt;p&gt;A new version number tells you a publisher published. It does not tell you what changed, and this comparison cannot see inside a release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removals: 75, from 66 publishers
&lt;/h2&gt;

&lt;p&gt;Six of the 75 had been first listed inside the same window. The registry's list stops returning a removed name, so what we record is "listed on one date, not listed on the next".&lt;/p&gt;

&lt;h2&gt;
  
  
  Status: 207 servers moved to deprecated, 137 of them in one day
&lt;/h2&gt;

&lt;p&gt;All 207 status changes in the week were &lt;code&gt;active&lt;/code&gt; to &lt;code&gt;deprecated&lt;/code&gt;. None went the other way. They came from 59 publishers.&lt;/p&gt;

&lt;p&gt;The shape is uneven: 4 to 19 a day, then 144 on 10-31. 137 of those 144 belong to one publisher, so that day is one publisher's decision, not a trend.&lt;/p&gt;

&lt;p&gt;We also know these status changes only because of a gap we closed during this week. Until 2026-10-29, a change of status at the same version was counted in the daily summary and never written back to our stored record, so the entry kept showing as active. A full comparison on 2026-10-28 found 53 such records. Since then, every status change is reconciled into the stored record the same day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you depend on the registry
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Poll more often than the changes arrive.&lt;/strong&gt; Averaged over the eight days, roughly 1,600 entries a day got a new latest version and roughly 650 servers a day were added. The registry's list API supports an &lt;code&gt;updated_since&lt;/code&gt; filter, so you can ask for only what changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch status as well as version.&lt;/strong&gt; A deprecation at the same version produces no new version. If you only watch versions, you will not see it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat a listing as a dated fact.&lt;/strong&gt; An entry that was current on Monday can be deprecated on Tuesday, and no one re-checks it unless someone does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin by exact version if you can.&lt;/strong&gt; The latest version of a large share of the registry moved in eight days.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I did not check
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What changed inside the updates. A new version could be a one-line fix or a rewrite; this comparison sees only the registry's own fields.&lt;/li&gt;
&lt;li&gt;Whether the 5,161 new servers work. Nothing was started and no tool was called.&lt;/li&gt;
&lt;li&gt;Why the 137 deprecations on 10-31 happened. I read the status, not the publisher's reason.&lt;/li&gt;
&lt;li&gt;Anything outside these eight days. A week with different release habits would show different shares.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  See it yourself
&lt;/h2&gt;

&lt;p&gt;The daily record is public: &lt;a href="https://protocolprobe.com/mcp/changes" rel="noopener noreferrer"&gt;protocolprobe.com/mcp/changes&lt;/a&gt; lists each day's new, removed and changed servers, there is an Atom feed, and the same data is available through the site's API. If your own count of a day disagrees with mine, tell me which day and what you got, and I will re-run that comparison.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Run security checks against your own MCP server in CI, and see the findings in GitHub</title>
      <dc:creator>md sultan mahamud</dc:creator>
      <pubDate>Fri, 30 Oct 2026 15:04:27 +0000</pubDate>
      <link>https://dev.to/sultan57git/run-security-checks-against-your-own-mcp-server-in-ci-and-see-the-findings-in-github-5936</link>
      <guid>https://dev.to/sultan57git/run-security-checks-against-your-own-mcp-server-in-ci-and-see-the-findings-in-github-5936</guid>
      <description>&lt;p&gt;Most MCP security tooling reads a server's tool &lt;strong&gt;descriptions&lt;/strong&gt;. That finds text that looks suspicious. It does not tell you what your server &lt;em&gt;does&lt;/em&gt; when a client calls it.&lt;/p&gt;

&lt;p&gt;This post is about a small command-line tool that does the second thing, for a server &lt;strong&gt;you&lt;/strong&gt; run: it starts your server, connects as an MCP client, calls it, and reports what it observed. It is called &lt;code&gt;protocolprobe-selftest&lt;/code&gt;, it is ISC-licensed, and the source is public: &lt;a href="https://github.com/protocolprobe/protocolprobe-tools" rel="noopener noreferrer"&gt;protocolprobe/protocolprobe-tools&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I build &lt;a href="https://protocolprobe.com" rel="noopener noreferrer"&gt;ProtocolProbe&lt;/a&gt;, a directory of dated facts about the servers in the official MCP registry. This tool is the part that runs on &lt;em&gt;your&lt;/em&gt; machine, not ours. We use it on nobody else's server: you point it at your own, and the results stay on your computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it once
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx protocolprobe-selftest &lt;span class="nt"&gt;--i-own-this-server&lt;/span&gt; &lt;span class="nt"&gt;--stdio&lt;/span&gt; node ./dist/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything after &lt;code&gt;--stdio&lt;/code&gt; is your server's own command line, so a Python server works the same way (needs &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx protocolprobe-selftest &lt;span class="nt"&gt;--i-own-this-server&lt;/span&gt; &lt;span class="nt"&gt;--stdio&lt;/span&gt; uv run &lt;span class="nt"&gt;--directory&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is started in a temporary folder, so keep &lt;code&gt;--directory .&lt;/code&gt; with &lt;code&gt;uv run&lt;/code&gt;; a plain &lt;code&gt;uv run server.py&lt;/code&gt; will not find your project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--i-own-this-server&lt;/code&gt; is required on every run. It is a statement, not a technicality: the tool is for servers you own or are authorised to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the output looks like
&lt;/h2&gt;

&lt;p&gt;The result is a count over the checks that actually ran, with the rest listed, for example "N of M checks passed". Two rules are deliberate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No letter grade, no weighting.&lt;/strong&gt; A single grade hides which check failed and why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checks that could not run are listed, never counted as passed.&lt;/strong&gt; If a check does not apply to your setup, or could not be tested, the output says so. A result where some checks did not run is marked partial.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two of the checks are off unless you turn them on, because they are the kind that change data or send malformed input to your server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx protocolprobe-selftest &lt;span class="nt"&gt;--i-own-this-server&lt;/span&gt; &lt;span class="nt"&gt;--allow-mutating&lt;/span&gt; &lt;span class="nt"&gt;--allow-fuzzing&lt;/span&gt; &lt;span class="nt"&gt;--out&lt;/span&gt; report.json &lt;span class="nt"&gt;--stdio&lt;/span&gt; node ./dist/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use those against a development copy, not against a server holding data you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put it in CI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx protocolprobe-selftest &lt;span class="nt"&gt;--i-own-this-server&lt;/span&gt; &lt;span class="nt"&gt;--fail-on-finding&lt;/span&gt; &lt;span class="nt"&gt;--sarif&lt;/span&gt; selftest.sarif &lt;span class="nt"&gt;--stdio&lt;/span&gt; node ./dist/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--fail-on-finding&lt;/code&gt; makes the job exit with code &lt;strong&gt;5&lt;/strong&gt; when a check needs attention (&lt;code&gt;VULNERABLE&lt;/code&gt; or &lt;code&gt;FOUND&lt;/code&gt;). Without it the exit code is 0 whatever the result.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--sarif &amp;lt;file&amp;gt;&lt;/code&gt; also writes the result as SARIF 2.1.0, which GitHub code scanning reads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only checks that need attention become SARIF results: &lt;code&gt;VULNERABLE&lt;/code&gt; is an error, &lt;code&gt;FOUND&lt;/code&gt; is a warning (a heuristic match for you to review). The score and the list of what did not run are kept in &lt;code&gt;runs[0].properties&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A finding here is about the &lt;strong&gt;running server&lt;/strong&gt;, not a line of code, so every result points at line 1 of one file. &lt;code&gt;--sarif-anchor &amp;lt;file&amp;gt;&lt;/code&gt; picks that file; by default it is the first file in your &lt;code&gt;--stdio&lt;/code&gt; command that is inside the current folder. With neither, the SARIF is valid but GitHub does not display the results, so run the command from your repository root.&lt;/p&gt;

&lt;p&gt;One thing that is easy to get wrong in GitHub Actions: a step that exits 5 stops the steps after it. Give the upload step &lt;code&gt;if: always()&lt;/code&gt;, or the SARIF is not uploaded in exactly the case where there are findings.&lt;/p&gt;

&lt;p&gt;A workflow of that shape looks like this (adapt the build and server command to your project):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mcp-selftest&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selftest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;security-events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci &amp;amp;&amp;amp; npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx protocolprobe-selftest --i-own-this-server --fail-on-finding --sarif selftest.sarif --stdio node ./dist/server.js&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selftest.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have not run this exact workflow end to end for this post; the command line and the exit-code behaviour are from the tool's tests and documentation. If your first run behaves differently, &lt;a href="https://github.com/protocolprobe/protocolprobe-tools/issues/new/choose" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; with the tool version, your Node version and what you ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  A server that is already deployed
&lt;/h2&gt;

&lt;p&gt;From version 0.2.0 the tool can test your own Streamable HTTP server at a URL, but only after the server proves you control that exact URL:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the command with &lt;code&gt;--url&lt;/code&gt;. It connects to nothing, prints a line such as &lt;code&gt;protocolprobe-proof=pp1-...&lt;/code&gt;, and exits with code 3.&lt;/li&gt;
&lt;li&gt;Put that line, on a line of its own, into your MCP server's &lt;code&gt;instructions&lt;/code&gt; text and deploy.&lt;/li&gt;
&lt;li&gt;Run the same command again within 30 minutes. It reads the instructions, and if the line is there it runs the checks on that same connection. The value is then used up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rules are enforced and tested: the value works once, only for the exact URL it was made for, expires after 30 minutes, and no redirect is followed. If your server needs a credential, you pass it through an &lt;strong&gt;environment variable&lt;/strong&gt; (&lt;code&gt;--bearer-env&lt;/code&gt;), never on the command line, and it is only sent to the proven URL and replaced with &lt;code&gt;[redacted]&lt;/code&gt; in everything the tool prints or writes.&lt;/p&gt;

&lt;p&gt;Be clear about what this proof is. It stops accidents and casual misuse: nobody can make someone else's server return a fresh random value. It is &lt;strong&gt;not enforcement&lt;/strong&gt;. The tool is open source and could be changed to skip it, and other tools connect to any URL with no proof at all. Use &lt;code&gt;--url&lt;/code&gt; only for a server you operate or are authorised to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it cannot tell you
&lt;/h2&gt;

&lt;p&gt;These limits are in the README too, and they matter more than the feature list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Over HTTP, fewer checks apply.&lt;/strong&gt; Two checks (a secret leaking into output, and a leak into logs) look for a fake secret planted in the process the tool starts, so they are marked not applicable when your server is at a URL. They are listed, not counted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Through a proxy, the same two are lost.&lt;/strong&gt; If you start your server via a stdio-to-HTTP proxy (&lt;code&gt;mcp-remote&lt;/code&gt; for example), the process under test is the proxy. The tool recognises a short list of common proxies; a proxy not on the list, or a wrapper script around one, is not detected, and those two checks can then come out clean without meaning anything. A server inside a container or virtual machine is the same case, and that case was not tested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A clean result is not a certificate.&lt;/strong&gt; It means these checks did not find a problem in this run, against this server, in this configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a runtime check at all
&lt;/h2&gt;

&lt;p&gt;When I ran the same battery against five widely used reference MCP servers, the one result that repeated everywhere was this: on every server that had a tool that changes data, the tool ran in a millisecond or two, with no error and no confirmation step. That is how the protocol is designed, and many clients ask the human for confirmation. But if your server is reached by a client that does not, nothing in the server stops it. That is a fact about behaviour, and it only shows up if you call the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, and tell me what is wrong
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx protocolprobe-selftest &lt;span class="nt"&gt;--i-own-this-server&lt;/span&gt; &lt;span class="nt"&gt;--stdio&lt;/span&gt; &amp;lt;your server &lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source, issues and discussions: &lt;a href="https://github.com/protocolprobe/protocolprobe-tools" rel="noopener noreferrer"&gt;github.com/protocolprobe/protocolprobe-tools&lt;/a&gt;. If a check gives a wrong result on your server, that is the most useful bug report I can get.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>mcp</category>
      <category>opensource</category>
    </item>
    <item>
      <title>"Our MCP directory said \"active\". The publisher had deprecated it five days earlier."</title>
      <dc:creator>md sultan mahamud</dc:creator>
      <pubDate>Thu, 29 Oct 2026 13:41:18 +0000</pubDate>
      <link>https://dev.to/sultan57git/our-mcp-directory-said-active-the-publisher-had-deprecated-it-five-days-earlier-9jk</link>
      <guid>https://dev.to/sultan57git/our-mcp-directory-said-active-the-publisher-had-deprecated-it-five-days-earlier-9jk</guid>
      <description>&lt;p&gt;I run ProtocolProbe, a directory of facts about every server in the official MCP Registry: each server gets a page, read from public records (the registry, npm, PyPI, GitHub), with a date on every fact.&lt;/p&gt;

&lt;p&gt;A publisher emailed us. They had renamed their server, marked the old registry entry as deprecated, and added a message pointing to the new name. Our page for the old entry still said &lt;strong&gt;active&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I told them it would update on our next daily run. That was wrong. This post is about why, and what we changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;Our daily job pulls the full registry list, compares it with yesterday's, and loads what changed: new servers, new versions, removed servers. For those, it re-reads the package and repository facts the same day.&lt;/p&gt;

&lt;p&gt;The publisher's change was none of those. The registry lets a publisher change an entry's &lt;strong&gt;status&lt;/strong&gt; (active → deprecated) and its &lt;strong&gt;status message&lt;/strong&gt; without publishing a new version. Same version, different status.&lt;/p&gt;

&lt;p&gt;Our job noticed it: it counted it as a "metadata-only" change. It just never loaded it. The page kept the old status, and the run still finished with &lt;code&gt;ok&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How big was it?
&lt;/h2&gt;

&lt;p&gt;One report can be a coincidence, so we compared every stored record with the registry list of that day, field by field (read-only, 51,834 servers):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;mismatches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;servers missing / extra&lt;/td&gt;
&lt;td&gt;0 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;version&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status (stored &lt;code&gt;active&lt;/code&gt;, registry &lt;code&gt;deprecated&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status message&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the parts the job was built for were exact, and the gap was precisely the case it skipped: entries deprecated by their publishers that we still showed as active. Our own record check "deprecated in the registry" gave the wrong answer for all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: reconcile what the registry owns, every day
&lt;/h2&gt;

&lt;p&gt;The registry is the source of truth for a few fields: status, status message, published and updated dates. Now, after every daily load, the job compares those fields for &lt;strong&gt;all&lt;/strong&gt; servers with the registry list and patches the ones that differ at the same version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a backup of every value before the first write, and a journal, so a crash mid-way resumes instead of half-patching;&lt;/li&gt;
&lt;li&gt;every patched document read back; a mismatch rolls the whole step back;&lt;/li&gt;
&lt;li&gt;a cap of 2,000 patches a day (a registry-wide change needs a person to look);&lt;/li&gt;
&lt;li&gt;each change recorded publicly with its date, so anyone can see what changed and when.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First run: &lt;strong&gt;59 servers patched&lt;/strong&gt; (some had changed since the count above). A second comparison right after: &lt;strong&gt;0 mismatches&lt;/strong&gt; across 52,138 servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: a job that says "ok" must check the page
&lt;/h2&gt;

&lt;p&gt;The deeper problem was not the missing field. It was that the job reported success without looking at what visitors see.&lt;/p&gt;

&lt;p&gt;So the job now ends with a live check: after the pages are refreshed, it takes a small fixed sample of the day's changed servers (new, new version, status patched) and opens their &lt;strong&gt;public pages&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We first tried reading our own public API instead. It was the wrong thing to check, for two reasons we found by reading our code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The API is not what was stale. The page was.&lt;/li&gt;
&lt;li&gt;The API answer is cached at the CDN and inside each function instance, and the instance copy comes back without a cache header. A check right after a load can read an old copy and raise a false alarm.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each server page now carries a small machine-readable marker in its head: version, status, a short hash of the status message (never the message itself), and the snapshot date. The job compares that with what it loaded.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A mismatch is a &lt;strong&gt;warning&lt;/strong&gt;, never a rollback.&lt;/li&gt;
&lt;li&gt;Our own failures (rate limits, timeouts) are "not checked", never a mismatch.&lt;/li&gt;
&lt;li&gt;If half the sample can't be checked, that is a warning too.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix 3: a date range, not a date
&lt;/h2&gt;

&lt;p&gt;While checking this, we found a quieter version of the same problem on our leaderboards. They said "facts checked 2026-10-29". That was the newest date among the records, which meant the servers that changed that day.&lt;/p&gt;

&lt;p&gt;Most stars and download counts behind the ranking had been read days earlier. The page now shows the range: "facts checked 2026-10-24 to 2026-10-29".&lt;/p&gt;

&lt;h2&gt;
  
  
  How stale does a directory get?
&lt;/h2&gt;

&lt;p&gt;We measured it. We re-read the free facts (npm/PyPI, OSV, GitHub) of 1,729 servers that had &lt;strong&gt;not&lt;/strong&gt; changed in the registry, a 1/30 slice of the catalogue. It took about 14 minutes and about 1,200 GitHub API requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;614 of them (35%)&lt;/strong&gt; had at least one fact that had changed since our last read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;396 download counts&lt;/li&gt;
&lt;li&gt;301 last-push dates&lt;/li&gt;
&lt;li&gt;99 star counts&lt;/li&gt;
&lt;li&gt;23 version-pin states&lt;/li&gt;
&lt;li&gt;7 repository states (not found, found again, archived)&lt;/li&gt;
&lt;li&gt;1 newly withdrawn package&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these show up as a registry change. We built a daily rotation that re-reads such a slice every day, so every server is re-read at least monthly. It is switched off until we have watched a few normal runs of the new checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell anyone building on a registry
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A listing is a snapshot.&lt;/strong&gt; The official MCP Registry checks an entry when it is published (each new version), and nothing re-checks it on a schedule. We asked for that upstream: &lt;a href="https://github.com/modelcontextprotocol/registry/issues/1715" rel="noopener noreferrer"&gt;registry issue #1715&lt;/a&gt;. If you mirror it, you inherit the same gap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same version doesn't mean same record.&lt;/strong&gt; Compare the fields the source owns, not just the version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the output, not the exit code.&lt;/strong&gt; Our job said &lt;code&gt;ok&lt;/code&gt; for days while pages were wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show the age of your data honestly.&lt;/strong&gt; A single date can quietly overstate how fresh it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say when you were wrong.&lt;/strong&gt; I told the publisher the wrong thing first. Admitting it took one email. Our corrections are logged at &lt;a href="https://protocolprobe.com/corrections" rel="noopener noreferrer"&gt;protocolprobe.com/corrections&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every change the job records is public, by day, at &lt;a href="https://protocolprobe.com/mcp/changes" rel="noopener noreferrer"&gt;protocolprobe.com/mcp/changes&lt;/a&gt;, and in a free API with no key (&lt;a href="https://protocolprobe.com/docs/api" rel="noopener noreferrer"&gt;/docs/api&lt;/a&gt;). If you run an MCP server and something on its page looks wrong, &lt;a href="https://protocolprobe.com/operators" rel="noopener noreferrer"&gt;/operators&lt;/a&gt; says how to tell us.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>security</category>
      <category>mcp</category>
    </item>
    <item>
      <title>"Your MCP server's tool list can be refused before the model sees it: the rules three AI APIs applied to 21,812 lists"</title>
      <dc:creator>md sultan mahamud</dc:creator>
      <pubDate>Wed, 28 Oct 2026 19:32:30 +0000</pubDate>
      <link>https://dev.to/sultan57git/your-mcp-servers-tool-list-can-be-refused-before-the-model-sees-it-the-rules-three-ai-apis-1e2l</link>
      <guid>https://dev.to/sultan57git/your-mcp-servers-tool-list-can-be-refused-before-the-model-sees-it-the-rules-three-ai-apis-1e2l</guid>
      <description>&lt;p&gt;We read the tool lists of MCP servers in the official MCP registry. Then we sent each list, once, to three small AI models through their vendors' Batch APIs: Claude Haiku 5.5, GPT-5-nano and Gemini 2.5 Flash. Each model was set to its lowest reasoning level and got one fixed request. That was 21,812 tool lists, one per server, with answers dated 2026-10-25 to 2026-10-26 (UTC).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No MCP server was contacted and no tool was run.&lt;/strong&gt; We sent only the tool names, descriptions and input schemas, exactly as each server publishes them.&lt;/p&gt;

&lt;p&gt;The finding most useful to server authors: &lt;strong&gt;each API checks the tool list before its model sees it, and each API has its own rules.&lt;/strong&gt; The same list can be accepted by one API and refused by another. A refusal is the API's answer about the list as written. It is not a fault in the server's code, but it does mean that client never offers your tools to the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each API refused, in its own words
&lt;/h2&gt;

&lt;p&gt;Counts are tool lists, one per server, from our run. An API can have other rules, and can change them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anthropic's API (456 lists refused)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;API message&lt;/th&gt;
&lt;th&gt;Lists&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;oneOf&lt;/code&gt; / &lt;code&gt;allOf&lt;/code&gt; / &lt;code&gt;anyOf&lt;/code&gt; at the top level of &lt;code&gt;input_schema&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;input_schema does not support oneOf, allOf, or anyOf at the top level&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;267&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool name outside &lt;code&gt;^[a-zA-Z0-9_-]{1,128}$&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String should match pattern '^[a-zA-Z0-9_-]{1,128}$'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;173&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two tools with the same name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tool names must be unique.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema not valid JSON Schema draft 2020-12&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JSON schema is invalid. It must match JSON Schema draft 2020-12 …&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top-level property key outside &lt;code&gt;^[a-zA-Z0-9_.-]{1,64}$&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Property keys should match pattern …&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  OpenAI's API (591 lists refused)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;API message&lt;/th&gt;
&lt;th&gt;Lists&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top level not &lt;code&gt;type: object&lt;/code&gt;, or has &lt;code&gt;oneOf&lt;/code&gt;/&lt;code&gt;anyOf&lt;/code&gt;/&lt;code&gt;allOf&lt;/code&gt;/&lt;code&gt;enum&lt;/code&gt;/&lt;code&gt;const&lt;/code&gt;/&lt;code&gt;not&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;schema must have type 'object' and not have 'oneOf'/'anyOf'/'allOf'/'enum'/'const'/'not' at the top level.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;262&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;More than 128 tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid 'tools': array too long. Expected an array with maximum length 128 …&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;171&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool name outside &lt;code&gt;^[a-zA-Z0-9_-]+$&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;string does not match pattern …&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;167&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A &lt;code&gt;pattern&lt;/code&gt; it does not read as a regex (every case used &lt;code&gt;\p{…}&lt;/code&gt; Unicode classes)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'^[\\p{L}\\p{N}_-]+$' is not a 'regex'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A number in the schema too large&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a numeric value in the function parameters is too large&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List longer than the input limit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Input tokens exceed the configured limit of 272000 tokens.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 128-tool limit is not stated on the chat completions API reference page we read on 2026-10-25. The refused lists ranged from 129 to 1,711 tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google's API (7 lists refused, plus 135 unexplained)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;API message&lt;/th&gt;
&lt;th&gt;Lists&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool name starts with a digit, or has a space, braces or square brackets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Invalid function name. Must start with a letter or an underscore. …&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For 4 of those 7 lists, the names started with a digit. That meets the docs text we read, which gives no rule for the first character, but the API still refused them. Google's API also answered &lt;code&gt;Request contains an invalid argument.&lt;/code&gt; for 135 more lists without naming a rule. Of those 135, 34 had tuple-style &lt;code&gt;items&lt;/code&gt; (an array of schemas) and 30 had two tools with the same name. No list that Google's API answered had either. That is an association we measured, not a reason Google gave.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist for MCP server authors
&lt;/h2&gt;

&lt;p&gt;From the rules above, a tool list is accepted by all three APIs in our run more often when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tool names use only &lt;code&gt;a-z A-Z 0-9 _ -&lt;/code&gt;, start with a letter, and stay at 64 characters or fewer.&lt;/li&gt;
&lt;li&gt;Every tool name in the list is unique.&lt;/li&gt;
&lt;li&gt;Each &lt;code&gt;inputSchema&lt;/code&gt; is &lt;code&gt;type: "object"&lt;/code&gt; at the top level, with no &lt;code&gt;oneOf&lt;/code&gt;/&lt;code&gt;anyOf&lt;/code&gt;/&lt;code&gt;allOf&lt;/code&gt;/&lt;code&gt;enum&lt;/code&gt;/&lt;code&gt;const&lt;/code&gt;/&lt;code&gt;not&lt;/code&gt; there.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pattern&lt;/code&gt; values avoid &lt;code&gt;\p{…}&lt;/code&gt; Unicode classes.&lt;/li&gt;
&lt;li&gt;Arrays use a single schema in &lt;code&gt;items&lt;/code&gt;, not a tuple.&lt;/li&gt;
&lt;li&gt;The server exposes 128 tools or fewer.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What these figures do not mean
&lt;/h2&gt;

&lt;p&gt;They describe three small models on the dates shown, each at its lowest reasoning setting. They do not describe the vendors' other models or a real client with its own system prompt. They are not a judgment of any server or publisher. We do not rank the models. Each model's own figures are on &lt;a href="https://protocolprobe.com/updates/three-models" rel="noopener noreferrer"&gt;the full page&lt;/a&gt;, and each server's own API message is on its server page.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>850 MCP servers describe a tool that moves money. Here is what the tool lists say.</title>
      <dc:creator>md sultan mahamud</dc:creator>
      <pubDate>Wed, 28 Oct 2026 18:02:29 +0000</pubDate>
      <link>https://dev.to/sultan57git/850-mcp-servers-describe-a-tool-that-moves-money-here-is-what-the-tool-lists-say-4faa</link>
      <guid>https://dev.to/sultan57git/850-mcp-servers-describe-a-tool-that-moves-money-here-is-what-the-tool-lists-say-4faa</guid>
      <description>&lt;p&gt;Most people ask whether an MCP server is &lt;em&gt;popular&lt;/em&gt;. Fewer ask what its tools are &lt;em&gt;allowed to do&lt;/em&gt; once an agent connects.&lt;/p&gt;

&lt;p&gt;I read the tool list of every MCP server I could open in the official registry, and counted what the tool descriptions say each tool does. No tool was called, anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;Registry list of 2026-10-27: 51,168 servers. I opened a live, read-only session with 25,335 of them and read the tool lists of &lt;strong&gt;23,655 servers (385,096 tools)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A server is counted under every kind it has a tool of, so the rows overlap and do not add up to 23,655.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What a tool's description says it does&lt;/th&gt;
&lt;th&gt;Servers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;reads or searches&lt;/td&gt;
&lt;td&gt;20,019&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;creates or changes data&lt;/td&gt;
&lt;td&gt;6,437&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sends messages&lt;/td&gt;
&lt;td&gt;2,696&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;runs code or commands&lt;/td&gt;
&lt;td&gt;1,540&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;moves money (payment or financial)&lt;/td&gt;
&lt;td&gt;850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;controls a browser or desktop&lt;/td&gt;
&lt;td&gt;481&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;manages infrastructure&lt;/td&gt;
&lt;td&gt;125&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Looking at what each server &lt;em&gt;mainly&lt;/em&gt; works on instead: 8,710 only answer questions and touch nothing outside themselves, 4,072 work on accounts and data in outside web services, 1,978 on files and code on the machine they run on, and 756 on money, payments or trading.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read these numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;These are an AI model's reading of each tool description, not a test.&lt;/strong&gt; Two differently worded runs had to agree on a label, otherwise it was left out. Nobody checked the labels by hand. On my own spot checks the tool kind agreed 93.8% of the time between the two runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A description is a claim by the publisher.&lt;/strong&gt; "Moves money" means the description says so. It does not mean the tool works, or that it is safe or unsafe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I called no tool.&lt;/strong&gt; Not one. Connecting only lists what the server offers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not every server could be read.&lt;/strong&gt; 25,833 of the 51,168 were not opened: 15,434 by the server's own choice (it needs a key or sign-in), 1,799 because of a defect in the package, 2,117 because of my own limits, 6,483 for a cause I could not determine. These counts say nothing about them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters if you build agents
&lt;/h2&gt;

&lt;p&gt;An agent with a payments tool and a "send message" tool in one session is a different risk from one that only searches. The registry's required fields are a name, a description and a version, so a listing alone does not tell you which of these a server has. Reading the tool list does.&lt;/p&gt;

&lt;p&gt;A practical use: before you add a server to an agent, look up which kinds of tool it has, and give the agent only what the task needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browse it yourself
&lt;/h2&gt;

&lt;p&gt;Each list opens the servers with that kind of tool, each with its source and the date it was read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://protocolprobe.com/mcp/servers/tool-type/payment-or-financial/1" rel="noopener noreferrer"&gt;Moves money&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://protocolprobe.com/mcp/servers/tool-type/execute-code-or-command/1" rel="noopener noreferrer"&gt;Runs code or commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://protocolprobe.com/mcp/servers/tool-type/control-browser-or-desktop/1" rel="noopener noreferrer"&gt;Controls a browser or desktop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://protocolprobe.com/mcp/servers/tool-type/send-message/1" rel="noopener noreferrer"&gt;Sends messages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://protocolprobe.com/mcp/servers/tool-type/changes-data/1" rel="noopener noreferrer"&gt;Any tool that changes something&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Method and corrections: &lt;a href="https://protocolprobe.com/methodology" rel="noopener noreferrer"&gt;protocolprobe.com/methodology&lt;/a&gt; and &lt;a href="https://protocolprobe.com/corrections" rel="noopener noreferrer"&gt;protocolprobe.com/corrections&lt;/a&gt;. The same data is in a free API (no key) and a citable dataset: &lt;a href="https://protocolprobe.com/docs/cite" rel="noopener noreferrer"&gt;protocolprobe.com/docs/cite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I built ProtocolProbe. If a label looks wrong to you, tell me in the comments and I will check it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fryi88rxtzqu3ddfjddrn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fryi88rxtzqu3ddfjddrn.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What the official MCP registry looks like when you check it twice</title>
      <dc:creator>md sultan mahamud</dc:creator>
      <pubDate>Sun, 25 Oct 2026 21:14:30 +0000</pubDate>
      <link>https://dev.to/sultan57git/what-the-official-mcp-registry-looks-like-when-you-check-it-twice-87g</link>
      <guid>https://dev.to/sultan57git/what-the-official-mcp-registry-looks-like-when-you-check-it-twice-87g</guid>
      <description>&lt;p&gt;I run &lt;a href="https://protocolprobe.com" rel="noopener noreferrer"&gt;ProtocolProbe&lt;/a&gt;, a free site that records dated facts about every server in the official MCP registry. We pulled the whole registry on 2026-10-04 and again on 2026-10-23, and we have pulled it daily since. This post is about what changed, what the registry itself checks and when, and how to reproduce the counts yourself. No server's tools were called to get any of it: everything below comes from the registry API, npm, PyPI, GitHub and OSV.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in 19 days
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;2026-10-04: 38,977 latest entries. 2026-10-23: 48,551. (2026-10-24: 49,445.)&lt;/li&gt;
&lt;li&gt;Between the two pulls: 9,803 new servers, 5,646 existing servers listing a newer version, and 229 servers no longer listed. The arithmetic closes: 38,977 + 9,803 − 229 = 48,551.&lt;/li&gt;
&lt;li&gt;The registry gives no reason for a removal, so we do not guess one. Each removed server's page stays up, marked as removed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The day-by-day record is at &lt;a href="https://protocolprobe.com/mcp/changes" rel="noopener noreferrer"&gt;protocolprobe.com/mcp/changes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the registry checks, and when
&lt;/h2&gt;

&lt;p&gt;This is from reading the registry's source (commit &lt;code&gt;c384c4b&lt;/code&gt;, read on 2026-10-06), so treat it as a statement about that code, not about anyone's intent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At publish time the validators fetch the exact listed npm or PyPI version and check the ownership token.&lt;/li&gt;
&lt;li&gt;I found nothing that re-validates an entry after it is published. &lt;code&gt;repository.url&lt;/code&gt; is checked with a regular expression only, and the schema requires only &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt;, so an entry with no package and no remote is allowed.&lt;/li&gt;
&lt;li&gt;On 2026-10-26 the registry's roadmap (&lt;code&gt;docs/design/roadmap.md&lt;/code&gt;) has no re-validation item. It also says its own phase labels are out of date as of 2026-08-10.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I opened &lt;a href="https://github.com/modelcontextprotocol/registry/issues/1715" rel="noopener noreferrer"&gt;issue #1715&lt;/a&gt; asking for periodic re-checks of published entries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a second look finds (dataset snapshot 2026-10-24)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What we counted&lt;/th&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Entries in the snapshot&lt;/td&gt;
&lt;td&gt;49,445&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote only / local package only / both / neither&lt;/td&gt;
&lt;td&gt;30,185 / 16,395 / 2,348 / 517&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entries whose linked GitHub repository exists&lt;/td&gt;
&lt;td&gt;29,039&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entries whose linked GitHub repository returns 404&lt;/td&gt;
&lt;td&gt;4,622 (13.7% of the 33,661 that link one)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entries with no GitHub link we could check&lt;/td&gt;
&lt;td&gt;15,784&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entries marked deprecated in the registry&lt;/td&gt;
&lt;td&gt;620&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One earlier number from the 2026-10-04 snapshot, which we have not re-run on the newer one: of 14,431 npm and PyPI entries, 157 (1.09%) listed a package or version that cannot be installed because it does not exist on npm or PyPI.&lt;/p&gt;

&lt;p&gt;What these numbers are not: a 404 can mean a deleted, private or moved repository, and we cannot tell which. A count of public records on a date is not a verdict on any server, and we do not rank or grade servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce the repository count
&lt;/h2&gt;

&lt;p&gt;The dataset is CC BY 4.0, on &lt;a href="https://doi.org/10.5281/zenodo.23386799" rel="noopener noreferrer"&gt;Zenodo&lt;/a&gt; (the DOI of all versions leads to the newest), &lt;a href="https://huggingface.co/datasets/sultan57/mcp-registry-facts" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt; and &lt;a href="https://www.kaggle.com/datasets/sultan57/mcp-registry-facts-protocolprobe" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt;. Standard library only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://huggingface.co/datasets/sultan57/mcp-registry-facts/resolve/main/servers.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repository&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;linked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;present&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repo-not-found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;repo-not-found&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;linked&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;linked&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 49445 {'none': 15784, 'present': 29039, 'repo-not-found': 4622} 13.7% of 33661
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran this on 2026-10-26 and got the output in the last comment. The CSV has 22 columns (&lt;code&gt;hosting_type&lt;/code&gt;, &lt;code&gt;lifecycle_status&lt;/code&gt;, &lt;code&gt;license_class&lt;/code&gt;, &lt;code&gt;maintenance&lt;/code&gt; and more); the full records are one JSON file per part, exactly as the &lt;a href="https://protocolprobe.com/docs/api" rel="noopener noreferrer"&gt;public API&lt;/a&gt; serves them.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you run an MCP server
&lt;/h2&gt;

&lt;p&gt;There is a small command that runs ProtocolProbe's runtime checks against a server you run yourself, on your own machine: &lt;code&gt;npx protocolprobe-selftest&lt;/code&gt;. It can fail a CI job (&lt;code&gt;--fail-on-finding&lt;/code&gt;) and write SARIF (&lt;code&gt;--sarif&lt;/code&gt;). It is separate from the data above, which never calls anyone's tools.&lt;/p&gt;

&lt;p&gt;Corrections are welcome: &lt;a href="https://protocolprobe.com/corrections" rel="noopener noreferrer"&gt;protocolprobe.com/corrections&lt;/a&gt; lists the ones we have made.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>The MCP registry checks a package at publish. Here's what drifts after that.</title>
      <dc:creator>md sultan mahamud</dc:creator>
      <pubDate>Sat, 24 Oct 2026 00:32:10 +0000</pubDate>
      <link>https://dev.to/sultan57git/the-mcp-registry-checks-a-package-at-publish-heres-what-drifts-after-that-4690</link>
      <guid>https://dev.to/sultan57git/the-mcp-registry-checks-a-package-at-publish-heres-what-drifts-after-that-4690</guid>
      <description>&lt;p&gt;When you install an MCP server from the official registry, you get exactly the package version the entry lists. The registry checks that version when the entry is published: it fetches it from npm or PyPI and confirms the package names the server.&lt;/p&gt;

&lt;p&gt;I read the registry's source to see what happens after that. The package check runs again &lt;strong&gt;only when the publisher edits the entry&lt;/strong&gt;. Nothing re-checks entries on a schedule. Repository links are checked against a URL pattern and never fetched.&lt;/p&gt;

&lt;p&gt;That's a reasonable design for a young registry, and it isn't anyone's mistake. But it means entries can drift. So I checked the whole registry against npm, PyPI and GitHub and counted what no longer matches.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I counted
&lt;/h2&gt;

&lt;p&gt;Registry snapshot 2026-10-04: &lt;strong&gt;38,977 servers&lt;/strong&gt; (the latest version of each name). Lookups ran on 2026-10-06.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Out of&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository link returns 404 (missing or private)&lt;/td&gt;
&lt;td&gt;4,298&lt;/td&gt;
&lt;td&gt;28,743 entries with a GitHub link&lt;/td&gt;
&lt;td&gt;15.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listed version is withdrawn upstream (npm deprecated / PyPI yanked)&lt;/td&gt;
&lt;td&gt;663&lt;/td&gt;
&lt;td&gt;14,431 npm/PyPI entries&lt;/td&gt;
&lt;td&gt;4.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linked repository is archived&lt;/td&gt;
&lt;td&gt;309&lt;/td&gt;
&lt;td&gt;24,256 reachable repos&lt;/td&gt;
&lt;td&gt;1.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listed package or version can't be installed at all&lt;/td&gt;
&lt;td&gt;157&lt;/td&gt;
&lt;td&gt;14,431 npm/PyPI entries&lt;/td&gt;
&lt;td&gt;1.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active, but no package, no remote and no website&lt;/td&gt;
&lt;td&gt;216&lt;/td&gt;
&lt;td&gt;38,977&lt;/td&gt;
&lt;td&gt;0.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Together, &lt;strong&gt;at least 5,192 of 38,977 entries (13.3%), more than 1 in 8&lt;/strong&gt;, had at least one of these.&lt;/p&gt;

&lt;p&gt;A few notes on reading this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The 157 are the clearest case of drift.&lt;/strong&gt; All of them were published after the registry's version check existed. If validation was on (it is by default), each one passed an exact-version check and the package or version disappeared later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Withdrawn doesn't mean malicious.&lt;/strong&gt; I classified the publishers' own deprecation messages. Most are renames, moved distributions and discontinued packages. A few say the backing service shut down, and 12 publishers cite a security issue. A registry-driven install still gets the withdrawn version, with no warning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 404 can be a renamed or private repo.&lt;/strong&gt; I don't tell those apart.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fix is now being tracked
&lt;/h2&gt;

&lt;p&gt;I opened a feature request on the registry, &lt;a href="https://github.com/modelcontextprotocol/registry/issues/1715" rel="noopener noreferrer"&gt;modelcontextprotocol/registry#1715&lt;/a&gt;. It asks to re-run the existing checks on a schedule and record the result (for example &lt;code&gt;lastVerifiedAt&lt;/code&gt; plus a status) instead of deleting anything.&lt;/p&gt;

&lt;p&gt;On 2026-10-23, a member of the MCP organization consolidated the older re-validation request (#93) and five endpoint-health reports into that issue, so periodic re-validation now has one implementation tracker. The direction in the thread: dated results, explicit "auth-required / transient / unknown" states, and &lt;strong&gt;no removal on a single failed check&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That last point matters. A missing repo or a yanked version is a fact a client should &lt;em&gt;see&lt;/em&gt;. It shouldn't silently turn into a takedown.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't say
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Nothing here says whether any server is safe or unsafe. I didn't run or call any server's tools. Everything comes from the registry and public package records.&lt;/li&gt;
&lt;li&gt;The numbers are a dated snapshot. The registry has grown since then, and I haven't recounted these figures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you publish an MCP server
&lt;/h2&gt;

&lt;p&gt;Five minutes of housekeeping:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your registry entry's &lt;code&gt;repository.url&lt;/code&gt;. Does it still load?&lt;/li&gt;
&lt;li&gt;Is the version you list still published, and not deprecated or yanked?&lt;/li&gt;
&lt;li&gt;If you renamed or moved the package, publish an update that points to the new one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full tables, with each figure's source and date: &lt;a href="https://protocolprobe.com/mcp/reports/registry-vs-reality" rel="noopener noreferrer"&gt;https://protocolprobe.com/mcp/reports/registry-vs-reality&lt;/a&gt;&lt;br&gt;
Dataset (CC BY 4.0, citable): &lt;a href="https://doi.org/10.5281/zenodo.23386799" rel="noopener noreferrer"&gt;https://doi.org/10.5281/zenodo.23386799&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built ProtocolProbe, a free directory that reads the registry's public data. Corrections welcome: &lt;a href="mailto:hello@protocolprobe.com"&gt;hello@protocolprobe.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>We re-checked 38,977 MCP registry entries against npm, PyPI and GitHub: 4,298 repository links no longer open.</title>
      <dc:creator>md sultan mahamud</dc:creator>
      <pubDate>Sat, 17 Oct 2026 19:28:23 +0000</pubDate>
      <link>https://dev.to/sultan57git/we-re-checked-38977-mcp-registry-entries-against-npm-pypi-and-github-4298-repository-links-no-339</link>
      <guid>https://dev.to/sultan57git/we-re-checked-38977-mcp-registry-entries-against-npm-pypi-and-github-4298-repository-links-no-339</guid>
      <description></description>
    </item>
    <item>
      <title>4,298 MCP registry entries link to a GitHub repo that returns 404</title>
      <dc:creator>md sultan mahamud</dc:creator>
      <pubDate>Fri, 16 Oct 2026 10:37:36 +0000</pubDate>
      <link>https://dev.to/sultan57git/4298-mcp-registry-entries-link-to-a-github-repo-that-returns-404-dei</link>
      <guid>https://dev.to/sultan57git/4298-mcp-registry-entries-link-to-a-github-repo-that-returns-404-dei</guid>
      <description>&lt;p&gt;I built &lt;a href="https://protocolprobe.com" rel="noopener noreferrer"&gt;ProtocolProbe&lt;/a&gt; on my own to read the official MCP registry and public package records. This post is one question: &lt;strong&gt;when a registry entry links to a repository, does the link open?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ask
&lt;/h2&gt;

&lt;p&gt;The registry checks that a repository URL is well-formed. As far as I read its source (2026-10-06), it does not fetch the URL, and it checks a package once, at publish time. So a link that worked at publish can stop working later, and a link that never worked is still accepted. I have not re-run that source read since.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method
&lt;/h2&gt;

&lt;p&gt;For every registry entry that links to a GitHub repository, I called GitHub's public repository API and recorded the HTTP status. Public records only. I did not run or call any MCP server.&lt;/p&gt;

&lt;p&gt;Registry snapshot: 2026-10-04. Lookups: 2026-10-06.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Of&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Entries whose linked repo returns HTTP 404&lt;/td&gt;
&lt;td&gt;4,298&lt;/td&gt;
&lt;td&gt;38,977 registry entries&lt;/td&gt;
&lt;td&gt;11.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same, counted by publisher&lt;/td&gt;
&lt;td&gt;2,594&lt;/td&gt;
&lt;td&gt;23,016 publishers&lt;/td&gt;
&lt;td&gt;11.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same, among entries that link to GitHub&lt;/td&gt;
&lt;td&gt;4,298&lt;/td&gt;
&lt;td&gt;28,743&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linked repo is archived (read-only)&lt;/td&gt;
&lt;td&gt;309&lt;/td&gt;
&lt;td&gt;24,256 entries with a reachable repo&lt;/td&gt;
&lt;td&gt;1.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What a 404 means
&lt;/h2&gt;

&lt;p&gt;GitHub returns 404 for a repository that is missing &lt;strong&gt;or private&lt;/strong&gt;. From outside I can't tell which. It could be a rename, a deletion, a private repo or a typo. It is not a judgement on the publisher, and the registry never promised to re-check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Figures are from 2026-10-06, not live.&lt;/li&gt;
&lt;li&gt;GitHub links only; other hosts are not counted.&lt;/li&gt;
&lt;li&gt;Counting by publisher is shown next to entries because a few publishers own many entries.&lt;/li&gt;
&lt;li&gt;No monthly re-check has run yet, so I can't say how these numbers move over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data and method
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Page with the figures and sources: &lt;a href="https://protocolprobe.com/mcp/reports" rel="noopener noreferrer"&gt;https://protocolprobe.com/mcp/reports&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Dataset (Zenodo): &lt;a href="https://doi.org/10.5281/zenodo.23386799" rel="noopener noreferrer"&gt;https://doi.org/10.5281/zenodo.23386799&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you see a flaw in the method, tell me in the comments.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
