<?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: IsoKron</title>
    <description>The latest articles on DEV Community by IsoKron (isokron).</description>
    <link>https://dev.to/isokron</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%2Forganization%2Fprofile_image%2F16071%2F10f73afb-ad3a-4d8a-b3ea-9ca85798ad84.png</url>
      <title>DEV Community: IsoKron</title>
      <link>https://dev.to/isokron</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isokron"/>
    <language>en</language>
    <item>
      <title>Lost updates in four MCP servers: GitHub, Grafana, PostHog and Kubernetes</title>
      <dc:creator>Joshua Bryan</dc:creator>
      <pubDate>Fri, 13 Nov 2026 03:32:25 +0000</pubDate>
      <link>https://dev.to/isokron/lost-updates-in-four-mcp-servers-github-grafana-posthog-and-kubernetes-2pon</link>
      <guid>https://dev.to/isokron/lost-updates-in-four-mcp-servers-github-grafana-posthog-and-kubernetes-2pon</guid>
      <description>&lt;p&gt;Two agents changed different fields of the same GitHub issue at the same moment, through GitHub's official MCP server. Both were told the write succeeded. In 19 of 30 pairs, one of the two changes was gone. GitHub's own per-field API, given the same two changes, lost none in 28.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I run IsoKron, which sells MCP audits. This is self-funded research, AI-assisted, and not commissioned or endorsed by GitHub, Grafana Labs, PostHog, the kubernetes-mcp-server maintainers or Red Hat. It was first published on &lt;a href="https://isokron.ai/research/mcp-lost-updates-four-servers?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=four-servers-2026-11" rel="noopener noreferrer"&gt;isokron.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We audited the MCP servers that GitHub, Grafana and PostHog publish for their own products, and the open-source &lt;code&gt;containers/kubernetes-mcp-server&lt;/code&gt;. In the first three, two writes sent at the same moment to the same object could lose one of the changes, and both callers were told their write had succeeded. In the Kubernetes server, a full copy of an object re-applied through the tool without its &lt;code&gt;resourceVersion&lt;/code&gt;, after another writer had changed a field, set that field back. This page puts the four results side by side. Each row links to the full audit.&lt;/p&gt;

&lt;p&gt;The reason to look at this class of bug now is that it was rare when one person used one client. It stops being rare when several agents work on the same backlog, dashboard or project at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;What was tested&lt;/th&gt;
&lt;th&gt;Lost through the MCP server&lt;/th&gt;
&lt;th&gt;Through the platform's own API&lt;/th&gt;
&lt;th&gt;Where the defect is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://isokron.ai/research/github-mcp-server-audit?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=four-servers-2026-11" rel="noopener noreferrer"&gt;GitHub MCP server v2.1.0&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Two updates to different issue fields of one issue&lt;/td&gt;
&lt;td&gt;19 of 30 pairs&lt;/td&gt;
&lt;td&gt;0 of 28 pairs, using GitHub's per-field update&lt;/td&gt;
&lt;td&gt;In the MCP tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://isokron.ai/research/grafana-mcp-server-audit?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=four-servers-2026-11" rel="noopener noreferrer"&gt;Grafana MCP server v2.2.0&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Two patches to different panels of one dashboard&lt;/td&gt;
&lt;td&gt;30 of 30 pairs, on Grafana 13 and on Grafana 11&lt;/td&gt;
&lt;td&gt;Legacy save endpoint: 30 of 30 on Grafana 13; 20 of 30 on Grafana 11, one save rejected in the other 10. Kubernetes-style API on Grafana 13: 0 of 30, one save rejected in every pair&lt;/td&gt;
&lt;td&gt;In the MCP tool and in the legacy save endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://isokron.ai/research/posthog-mcp-server-audit?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=four-servers-2026-11" rel="noopener noreferrer"&gt;PostHog MCP server&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Two updates to different fields of one insight&lt;/td&gt;
&lt;td&gt;7 of 11 pairs, through the hosted server&lt;/td&gt;
&lt;td&gt;6 of 11 pairs&lt;/td&gt;
&lt;td&gt;In PostHog's API; the MCP server passes it through&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://isokron.ai/research/kubernetes-mcp-server-audit?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=four-servers-2026-11#forced-apply-and-lost-updates-upstreams-choice-measured-not-filed" rel="noopener noreferrer"&gt;Kubernetes MCP server v0.1.2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A full copy re-applied through the tool after another writer changed a field&lt;/td&gt;
&lt;td&gt;Without &lt;code&gt;resourceVersion&lt;/code&gt;, the other writer's change lost: replicas 10 of 10, a ConfigMap key 10 of 10. With the stale &lt;code&gt;resourceVersion&lt;/code&gt; kept: refused with 409 in 10 of 10&lt;/td&gt;
&lt;td&gt;The same body without force, straight to the API: 409 conflict, 3 of 3&lt;/td&gt;
&lt;td&gt;Not filed as a bug: the tool applies with &lt;code&gt;force: true&lt;/code&gt;, which upstream chose on purpose&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Figure 1: Two simultaneous writes to one object, through three of the MCP servers and through each vendor’s own API&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Pairs that lost a change&lt;/th&gt;
&lt;th&gt;Pairs with one call rejected (conflict)&lt;/th&gt;
&lt;th&gt;Pairs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub MCP server v2.1.0&lt;/td&gt;
&lt;td&gt;Through the MCP server&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub MCP server v2.1.0&lt;/td&gt;
&lt;td&gt;GitHub’s per-field update&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana MCP server v2.2.0, on Grafana 13&lt;/td&gt;
&lt;td&gt;Through the MCP server&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana MCP server v2.2.0, on Grafana 13&lt;/td&gt;
&lt;td&gt;Legacy save endpoint&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana MCP server v2.2.0, on Grafana 13&lt;/td&gt;
&lt;td&gt;Kubernetes-style API&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana MCP server v2.2.0, on Grafana 11&lt;/td&gt;
&lt;td&gt;Through the MCP server&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana MCP server v2.2.0, on Grafana 11&lt;/td&gt;
&lt;td&gt;Legacy save endpoint&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostHog MCP server&lt;/td&gt;
&lt;td&gt;Through the hosted MCP server&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostHog MCP server&lt;/td&gt;
&lt;td&gt;PostHog’s API&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: the table above, from the three linked audits.&lt;/p&gt;

&lt;p&gt;A pair counts as lost when both calls returned success and one of the two changes was missing afterwards. All pairs were deliberately simultaneous. The figure covers the three servers tested with simultaneous pairs; the Kubernetes trials were a stale copy re-applied after another writer's change, so they are in the table only. None of these numbers says how often this happens in normal use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four different causes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub: the MCP tool rewrites more than it was asked to change.&lt;/strong&gt; To set one issue field, the tool reads every field value, merges in the change and writes the whole set back, with no version check. Two agents changing different fields overwrite each other. GitHub's own per-field update does not have the problem, which is why the control lost nothing. The MCP layer introduced the loss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grafana: the MCP tool switches off a check, and the endpoint behind it has a gap of its own.&lt;/strong&gt; Patch mode reads the dashboard, applies the patch and saves the whole dashboard with overwrite forced on, so Grafana's version check never runs. Grafana rejected a stale save in 10 of 10 trials when the check was allowed to run. But two saves that arrive at exactly the same moment through the legacy save endpoint were both accepted even with the check on. Grafana 13's Kubernetes-style API did not have that gap: it rejected one of two simultaneous saves in 30 of 30 pairs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostHog: the loss is in the product's API.&lt;/strong&gt; The same two requests sent straight to PostHog's API lost a write about as often as they did through the MCP server. The MCP server added nothing and removed nothing. An agent hits it more easily than a person does, because agents work in parallel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes: forced apply is upstream's choice, measured, not filed.&lt;/strong&gt; &lt;code&gt;resources_create_or_update&lt;/code&gt; applies with server-side apply and &lt;code&gt;force: true&lt;/code&gt;. Upstream chose this on purpose (issue &lt;a href="https://github.com/containers/kubernetes-mcp-server/issues/799" rel="noopener noreferrer"&gt;799&lt;/a&gt;). The API has two protections for this edit: server-side apply without force refuses to take over fields another manager owns, and a &lt;code&gt;resourceVersion&lt;/code&gt; in the body makes a stale write fail. Forcing switches off the first, so only the second remains, and only if the client keeps the &lt;code&gt;resourceVersion&lt;/code&gt; it read. Neither native session in that audit ever sent a &lt;code&gt;resourceVersion&lt;/code&gt;: in the concurrent-edit job, another writer changed &lt;code&gt;MAX_CONNECTIONS&lt;/code&gt; from 100 to 200 about half a second after the agent read the ConfigMap, the agent's forced apply set it back to 100 four seconds later, and the agent told the user that the other keys were unchanged. Where the server does use optimistic concurrency, it held: &lt;code&gt;resources_scale&lt;/code&gt; sends the &lt;code&gt;resourceVersion&lt;/code&gt; it read, an injected conflict came back as an error with nothing changed (3 of 3), and real contention surfaced 10 of 10 times. A related design gap, K8S-04, is that &lt;code&gt;resources_delete&lt;/code&gt; sends no uid or &lt;code&gt;resourceVersion&lt;/code&gt; precondition, so it deleted an object recreated after the agent's read (10 of 10; &lt;code&gt;kubectl&lt;/code&gt; has no uid precondition either).&lt;/p&gt;

&lt;p&gt;The distinction matters when you read an audit of any MCP server. "The MCP server loses writes" can mean the server is wrong, that it faithfully exposes a backend that is, or that it made a deliberate trade-off. The control against the platform's own API is what tells them apart, and it should be in the report.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened next
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Reported&lt;/th&gt;
&lt;th&gt;Status on 12 November 2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/github/github-mcp-server/issues/3751" rel="noopener noreferrer"&gt;Issue 3751&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Our fix is open as &lt;a href="https://github.com/github/github-mcp-server/pull/3781" rel="noopener noreferrer"&gt;pull request 3781&lt;/a&gt;, with no maintainer review as of 12 November 2026. In our test of that approach a patched build lost 0 of 12 pairs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/grafana/mcp-grafana/pull/1518" rel="noopener noreferrer"&gt;Pull request 1518&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Open; as of 12 November 2026 it was awaiting maintainer review, rebased onto the current main branch and mergeable. The proposed fix saves through the Kubernetes-style API where it exists and lost 0 of 60 pairs on Grafana 13. Grafana 11 has only the legacy endpoint; there the restored version check reports stale saves but still lost 14 of 30 simultaneous pairs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostHog&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/PostHog/posthog/pull/131519" rel="noopener noreferrer"&gt;Pull request 131519&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Fixed for insights. A maintainer re-created the change as &lt;a href="https://github.com/PostHog/posthog/pull/131620" rel="noopener noreferrer"&gt;pull request 131620&lt;/a&gt;, with IsoKron as co-author, and merged it about 17 hours after we opened ours. Cohorts and dashboards lost writes in the same tests and, as of 12 November 2026, are not yet fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;Not filed&lt;/td&gt;
&lt;td&gt;Forced apply is a deliberate upstream decision, and K8S-04 is a design gap; neither was filed. The research build adds &lt;code&gt;resources_patch&lt;/code&gt; with an optional &lt;code&gt;resourceVersion&lt;/code&gt; precondition, in the shape of upstream's open proposal &lt;a href="https://github.com/containers/kubernetes-mcp-server/issues/1352" rel="noopener noreferrer"&gt;1352&lt;/a&gt;, and makes apply without force the default. That reverses a deliberate upstream decision, so it is a proposal, not a fix&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;PostHog's response is the one to copy: a small fix, reviewed and merged the next morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not show
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The counts are small: 11 to 30 pairs per cell for GitHub, Grafana and PostHog, with the two calls released together on purpose, and 10 trials per arm for Kubernetes (3 for the API control and for the injected scale conflict), on one machine, one cluster or one test account.&lt;/li&gt;
&lt;li&gt;Real agents did task work through the server in all four audits. The lost-update counts did not come from those sessions: they come from scripted pairs and trials, so they show that a collision or a stale copy loses a write, not how often agents collide on their own.&lt;/li&gt;
&lt;li&gt;Four servers is not a survey. We chose them because they are widely used and open source, not at random.&lt;/li&gt;
&lt;li&gt;In the three simultaneous-pair cases the lost change can be put back by writing it again, and at Grafana it stays in the dashboard's version history. The harm is that nothing tells the agent or the user that it needs to.&lt;/li&gt;
&lt;li&gt;The findings were found with AI assistance and checked as each audit describes. We sell audits of this kind, so read the method sections with that in mind.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to check a server you run
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pick a tool that updates part of an object: one field, one panel, one tag.&lt;/li&gt;
&lt;li&gt;Send two calls that change different parts of the same object at the same moment, from two separate server processes. Repeat at least ten times.&lt;/li&gt;
&lt;li&gt;Read the object back. If both calls said success and one change is missing, you have a lost update.&lt;/li&gt;
&lt;li&gt;Repeat with the same two changes sent straight to the product's API. If the API loses nothing, the MCP tool is the cause. If the API loses them too, the fix belongs in the API, and the tool should at least surface a conflict where the API offers one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full method, including the controls and the ways we got it wrong before getting it right, is in &lt;a href="https://isokron.ai/research/how-to-test-an-mcp-server?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=four-servers-2026-11" rel="noopener noreferrer"&gt;How to test an MCP server without fooling yourself&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The method&lt;/strong&gt;, with the controls and the mistakes we made first: &lt;a href="https://isokron.ai/research/how-to-test-an-mcp-server?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=four-servers-2026-11" rel="noopener noreferrer"&gt;How to test an MCP server without fooling yourself&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/isokron-ai/github-mcp-writepath-experiment" rel="noopener noreferrer"&gt;isokron-ai/github-mcp-writepath-experiment&lt;/a&gt; is v2.1.0 plus the fixes as separate commits; &lt;code&gt;go test ./...&lt;/code&gt; runs the upstream tests plus the ones added for each fix. Its concurrent-pair harness is tied to a private test repository and isn't published yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostHog:&lt;/strong&gt; &lt;a href="https://github.com/isokron-ai/posthog-mcp-writepath-experiment" rel="noopener noreferrer"&gt;isokron-ai/posthog-mcp-writepath-experiment&lt;/a&gt;, with the benchmark harness in &lt;code&gt;harness/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes:&lt;/strong&gt; &lt;a href="https://github.com/isokron-ai/kubernetes-mcp-writepath-experiment" rel="noopener noreferrer"&gt;isokron-ai/kubernetes-mcp-writepath-experiment&lt;/a&gt;, with the frozen harness; &lt;code&gt;for b in baseline fixed research; do harness/build/build.sh $b; done&lt;/code&gt; builds all three binaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grafana:&lt;/strong&gt; the fix is &lt;a href="https://github.com/grafana/mcp-grafana/pull/1518" rel="noopener noreferrer"&gt;pull request 1518&lt;/a&gt; upstream.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your numbers differ from ours, open an issue on the matching repository. Reproductions that disagree are the most useful feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How this was tested.&lt;/strong&gt; AI agents run much of the testing: scripted checks, attempts to disprove each finding, and real tasks that use the server the way an agent would. I also test every server myself on a separate machine and check each finding against the AI results. A finding appears here only if it holds up in both. — &lt;a href="https://isokron.ai/about?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=four-servers-2026-11" rel="noopener noreferrer"&gt;Joshua Bryan&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this piece: Every figure is taken from the four linked audits, which give the method, the controls and the limits for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;Have you seen a write tool report success and lose data? Which MCP server should we test next? Tell me in the comments.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>go</category>
      <category>testing</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
