<?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: Willian Pinho</title>
    <description>The latest articles on DEV Community by Willian Pinho (@willianpinho).</description>
    <link>https://dev.to/willianpinho</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%2F1577086%2Fcf486936-bbac-4cbe-800d-e12957acb223.jpg</url>
      <title>DEV Community: Willian Pinho</title>
      <link>https://dev.to/willianpinho</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/willianpinho"/>
    <language>en</language>
    <item>
      <title>Fail-close: the tool-access default every AI agent should ship with</title>
      <dc:creator>Willian Pinho</dc:creator>
      <pubDate>Wed, 15 Jul 2026 14:45:52 +0000</pubDate>
      <link>https://dev.to/willianpinho/fail-close-the-tool-access-default-every-ai-agent-should-ship-with-179p</link>
      <guid>https://dev.to/willianpinho/fail-close-the-tool-access-default-every-ai-agent-should-ship-with-179p</guid>
      <description>&lt;h1&gt;
  
  
  Fail-close: the tool-access default every AI agent should ship with
&lt;/h1&gt;

&lt;p&gt;I spent the better part of sixteen years building payment platforms. The first principle you internalize there, before any framework or pattern, is that the safe state is the closed state. A transaction that can't be verified doesn't get a "probably fine" — it gets declined. A service that loses its dependency doesn't keep serving stale balances — it stops. You learn to design systems where the failure mode is refusal, not improvisation.&lt;/p&gt;

&lt;p&gt;Then I started building AI agents wired to real tools through MCP, and I watched the industry reach for the opposite default. Agents ship with broad tool access, allow-by-default, and a vague intention to "add guardrails later." That instinct is backwards, and it's going to cost teams who don't fix it before they go to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "fail-close" actually means for an agent
&lt;/h2&gt;

&lt;p&gt;Fail-close is a default you choose before the first tool is registered, not a guardrail you bolt on after. It means the default answer to "can this agent call this tool right now?" is no, and every yes is an explicit, scoped, auditable grant.&lt;/p&gt;

&lt;p&gt;Most agent setups today do the reverse. You register a set of MCP servers, the model gets the full menu, and the only thing standing between a hallucinated plan and a destructive action is the model's own judgment plus maybe a confirmation prompt the user clicks through on autopilot. That's allow-by-default with a speed bump. It works in demos. It fails the first time the model decides that the cleanest path to "tidy up the staging database" runs through a &lt;code&gt;DELETE&lt;/code&gt; it was never supposed to reach.&lt;/p&gt;

&lt;p&gt;A fail-close agent inverts that. The agent starts with zero tool authority. Access is granted per tool, ideally per operation, against an identity and a context. When something is ambiguous — an unrecognized server, a tool outside the current task scope, a parameter that looks like it touches production — the system denies and surfaces the denial instead of guessing.&lt;/p&gt;

&lt;p&gt;The distinction matters because models are non-deterministic and your blast radius is not. You cannot reason about what the model &lt;em&gt;won't&lt;/em&gt; do. You can only constrain what the surrounding system &lt;em&gt;will permit&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks when you don't
&lt;/h2&gt;

&lt;p&gt;The failures aren't exotic. They're the boring, predictable consequences of giving a probabilistic system deterministic power without a fence.&lt;/p&gt;

&lt;p&gt;Give an agent read and write access to the same datastore and it will eventually write when you wanted it to read, because the boundary between "summarize these records" and "update these records" lives only in the prompt, and prompts are suggestions. Hand it shell access and a filesystem MCP, and somewhere down the line it runs a command that's correct in isolation and catastrophic in context. The same goes for an internal API reached with a service token: a confused enough plan will call the destructive endpoint with full confidence.&lt;/p&gt;

&lt;p&gt;None of these require malice or a clever injection attack. They're just the base rate of a system that improvises under uncertainty being handed tools that don't. Add prompt injection from untrusted content the agent ingests, and the allow-by-default posture stops being a reliability problem and becomes a security one. The injected instruction doesn't need to break out of a sandbox if there was never a sandbox to break out of.&lt;/p&gt;

&lt;p&gt;When I owned security review for shipping AI features, the question I asked first was never "is the model good." It was "what is the worst single tool call this thing can make, and who decided it was allowed to." If the answer to the second half was "nobody, it just had access," the feature didn't ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance and RBAC for MCP tools
&lt;/h2&gt;

&lt;p&gt;One thing to be clear about first: the MCP protocol itself does not define roles, authorization, or RBAC. It describes how a client and server talk, not who is allowed to call what. That control plane is yours to build, and the natural place for it is the host application sitting between the model and the servers, the layer that sees every &lt;code&gt;tools/call&lt;/code&gt; before it goes out and can decide to refuse it.&lt;/p&gt;

&lt;p&gt;RBAC, scoped service identities, and audited grants already run every bank API and every CI deploy key in production. An agent is just another non-human principal asking for access. We have the machinery; we just have to stop pretending agents are a special category that gets to skip it.&lt;/p&gt;

&lt;p&gt;Treat every MCP tool as a privileged operation behind role-based access control. The agent runs under an identity. That identity has a role. The role grants a specific, enumerated set of tool operations, scoped to an environment. A research agent gets read access to a knowledge base and nothing that writes. A deployment agent gets a narrow set of operations against staging and a separate, more guarded grant for production that requires a stronger gate.&lt;/p&gt;

&lt;p&gt;Three things make this real rather than theatre.&lt;/p&gt;

&lt;p&gt;Grants are per-operation, not per-server. Registering a database MCP server should not hand over its entire surface. Read and write are different privileges and belong to different roles, the same way you'd never give an analyst the production write credential just because they need to query.&lt;/p&gt;

&lt;p&gt;Logging comes next: every tool call carries the identity, the role, the arguments, and the decision. You want to be able to answer "what did this agent do and what was it allowed to do" after the fact, with a trail, not a guess. The denials matter as much as the approvals; a spike in denied calls is a signal that the agent's plan has drifted from its scope.&lt;/p&gt;

&lt;p&gt;And the dangerous grants pass through a human or a stronger policy gate before they're active. The point of fail-close isn't to block everything forever. It's to make sure that anything with real blast radius is a decision someone made on purpose, with their name on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The instinct, ported
&lt;/h2&gt;

&lt;p&gt;I maintain an MCP server that runs across several agent clients, including Claude Code and Cursor. Building and operating it taught me how casually broad the default tool surface tends to be, and how little friction there is between a registered server and full access to whatever it exposes. The convenience is real. So is the exposure.&lt;/p&gt;

&lt;p&gt;The reliability instinct from payments transfers cleanly: the safe state is the closed state, and you earn your way out of it one explicit grant at a time. We learned to stop treating "the agent can't do that yet" as a limitation. In production, it is the whole point.&lt;/p&gt;

&lt;p&gt;The shift is small to describe and easy to defer. Choose the default before you register the first tool, not after the first incident. Everything else in this post is just the consequence of getting that one decision right while it is still cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices" rel="noopener noreferrer"&gt;Model Context Protocol — Security Best Practices&lt;/a&gt; — the protocol's official guidance on consent, confused-deputy risks, and host-side controls.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools" rel="noopener noreferrer"&gt;Model Context Protocol — Tools&lt;/a&gt; — defines tools as model-controlled and calls for trust boundaries and human-in-the-loop confirmation.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://genai.owasp.org/llmrisk/llm01-prompt-injection/" rel="noopener noreferrer"&gt;OWASP — LLM01: Prompt Injection&lt;/a&gt; — why untrusted content can redirect an agent's actions.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://genai.owasp.org/llmrisk/llm062025-excessive-agency/" rel="noopener noreferrer"&gt;OWASP — LLM06: Excessive Agency&lt;/a&gt; — the blast-radius risk of granting agents excessive functionality, permissions, or autonomy; recommends least-privilege scoping.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://csrc.nist.gov/glossary/term/least_privilege" rel="noopener noreferrer"&gt;NIST — Least Privilege (glossary)&lt;/a&gt; — the foundational principle behind per-operation, role-scoped tool grants.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>agents</category>
      <category>security</category>
    </item>
    <item>
      <title>MCP in production: what I learned maintaining a server that works in Claude Code, Cursor, and Gemini</title>
      <dc:creator>Willian Pinho</dc:creator>
      <pubDate>Fri, 03 Jul 2026 16:39:00 +0000</pubDate>
      <link>https://dev.to/willianpinho/mcp-in-production-what-i-learned-maintaining-a-server-that-works-in-claude-code-cursor-and-gemini-5dg0</link>
      <guid>https://dev.to/willianpinho/mcp-in-production-what-i-learned-maintaining-a-server-that-works-in-claude-code-cursor-and-gemini-5dg0</guid>
      <description>&lt;h1&gt;
  
  
  MCP in production: what I learned maintaining a server that works in Claude Code, Cursor, and Gemini
&lt;/h1&gt;

&lt;p&gt;I maintain an MCP server that ended up running inside Claude Code, Cursor, and Gemini CLI. I did not plan for three clients. I built it for one, and then watched the other two start exercising paths I never tested, in ways the spec technically allowed but I never imagined.&lt;/p&gt;

&lt;p&gt;That gap, between what the protocol permits and what a client actually does, is where most of the real lessons live. MCP looks simple on the surface: you expose a few tools, the model picks one and calls it, you return a result. That surface is exactly what fools you. The lessons below are the ones that only show up once a caller you didn't build for starts driving the server. Here is what survived contact with real usage, and what I deleted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model is a hostile-by-accident caller
&lt;/h2&gt;

&lt;p&gt;Every backend engineer learns to treat input as untrusted. With MCP, the caller is a language model, and it is untrusted in a stranger way. It is not malicious. It is confidently wrong at a rate you do not see in human users.&lt;/p&gt;

&lt;p&gt;A human who gets a confusing tool response stops and asks. A model retries. It will call your tool with a slightly mangled argument, get an error, reword the argument, and call again, sometimes a dozen times in a loop, because retrying is the cheapest action it has. Early on, one of my tools accepted a file offset and a length. A model would request a length that ran past the end of the file, get an error, and then ask for the same thing with the offset nudged by one. My logs filled with near-identical failures that no human would ever produce.&lt;/p&gt;

&lt;p&gt;The fix was not better validation messages, though I added those. It helps to know that MCP gives you two different ways to signal trouble: a protocol-level JSON-RPC error, which tends to read as "the call itself was malformed," and a normal tool result flagged with &lt;code&gt;isError&lt;/code&gt;, whose text the model actually reads and can act on. A hard protocol error invites the blind retry; a flagged result with a usable message invites a correction. The deeper fix was making the tool harder to misuse in the first place: clamp the range to the file instead of erroring, return what exists, and say so in the response. The model reads that, adjusts, and moves on. I stopped writing errors for a careful human reader and started writing responses a confused model could recover from without a loop. Once I saw it that way, half my "validation" work turned out to be the wrong shape entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail-close is the only default that survives three clients
&lt;/h2&gt;

&lt;p&gt;The deleted code is the part worth talking about. My first version had a permissive fallback: when a tool received an ambiguous or partially valid request, it tried to do something reasonable. That worked fine in Claude Code, where I had tested it. It broke in subtle ways the moment Cursor and Gemini drove it, because each client frames requests differently, truncates context differently, and decides differently when to call a tool at all.&lt;/p&gt;

&lt;p&gt;A "reasonable default" is a guess about caller intent. Three clients meant three different intents behind the same malformed request, and my guess was right for one of them at best. So I tore the fallback out and made the server fail closed. If a request is ambiguous, refuse it and explain what a valid request looks like. The model gets a clear next step instead of a silent wrong answer.&lt;/p&gt;

&lt;p&gt;This sounds obvious written down. It did not feel obvious while I was deleting code that worked in my one tested client to fix behavior in two I had not. Fail-close costs you a bit of apparent helpfulness and buys you the thing that matters more: a server that does the same predictable thing regardless of which agent is holding the other end. I have spent sixteen years on systems where the wrong silent default is how you lose money, including payment platforms serving over ten million users at three-nines uptime. Fail-close was not a new lesson. MCP just made me relearn it in an unfamiliar shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  You cannot debug what you cannot replay
&lt;/h2&gt;

&lt;p&gt;For the first weeks I had almost no idea what was actually happening inside a session. The model called my tools, things mostly worked, and when they did not, I had a vague bug report and no way to reproduce it. The non-determinism of the caller meant "run it again" produced a different sequence of calls.&lt;/p&gt;

&lt;p&gt;What changed everything was logging each tool invocation as a structured record: the arguments as received, the response as returned, the timing, and which client sent it. Not application logs, an event trail I could replay and reason about. Once I had that, the loops surfaced, the client-specific framing differences were suddenly obvious, and the bugs stopped being ghost stories. Observability is not a nice-to-have you add when an MCP server gets popular. It is the thing that lets you understand a caller you do not control. I would build it on day one next time, before the first real user.&lt;/p&gt;

&lt;p&gt;The second-order benefit surprised me. The event trail also told me which tools were never called. A model will quietly ignore a tool whose description does not earn its place, and you will never know unless you are counting. I removed two tools that looked useful to me and were invisible to every model that touched the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool descriptions are an interface, not documentation
&lt;/h2&gt;

&lt;p&gt;The part I underestimated most: the model chooses tools based on their names and descriptions, and nothing else. There is no onboarding, no docs site, no support channel. The description is the entire contract, and it is read by a system that interprets language statistically rather than literally.&lt;/p&gt;

&lt;p&gt;Tightening descriptions moved usage more than any code change I made. A tool that was being called in the wrong situations got called correctly once I rewrote its description to state plainly what it was for and what it was not for. This is closer to API design than to writing docs, and it rewards the same discipline: name the thing precisely, scope it narrowly, say what it does not do. I had shipped a KYC AI pipeline in three weeks once by being ruthless about scope, and the same instinct applied here. A narrow tool with a sharp description beats a flexible tool the model cannot reliably aim.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this points at
&lt;/h2&gt;

&lt;p&gt;None of this is exotic. It is ordinary production engineering, applied to a caller that behaves unlike any client you have built for before: non-deterministic, retry-happy, and reading your interface through a probabilistic lens. The teams I see struggling with agents in production are not failing at the model layer. They are failing at the boundary, where governance, fail-close defaults, and observability decide whether an agent is a reliable colleague or a liability you cannot debug.&lt;/p&gt;

&lt;p&gt;So if you take one thing from this, let it be where to point your attention. The interesting engineering with MCP is not in the model and not in the tools. It is in the thin layer between them, the part that is easy to skip in a demo and impossible to skip in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/docs/learn/server-concepts" rel="noopener noreferrer"&gt;Understanding MCP servers — server concepts (modelcontextprotocol.io)&lt;/a&gt; — tools are model-controlled: the model discovers and invokes them based on their names and descriptions.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools" rel="noopener noreferrer"&gt;MCP specification, server tools (2025-06-18)&lt;/a&gt; — tool definition (name, description, input schema) and the two error-reporting mechanisms: protocol-level JSON-RPC errors versus a result with &lt;code&gt;isError&lt;/code&gt; set, whose text the model can read and recover from.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/docs/learn/architecture" rel="noopener noreferrer"&gt;MCP architecture overview (modelcontextprotocol.io)&lt;/a&gt; — host, client, and server roles, and why the boundary between them is where production behavior is decided.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices" rel="noopener noreferrer"&gt;MCP — Security Best Practices (2025-06-18)&lt;/a&gt; — consent, confused-deputy risk, and the host-side controls that make fail-close defaults enforceable.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://genai.owasp.org/llmrisk/llm062025-excessive-agency/" rel="noopener noreferrer"&gt;OWASP — LLM06: Excessive Agency&lt;/a&gt; — the blast-radius risk of handing an agent broad tool functionality and permissions without scoping.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://genai.owasp.org/llmrisk/llm01-prompt-injection/" rel="noopener noreferrer"&gt;OWASP — LLM01: Prompt Injection&lt;/a&gt; — why a model driving your tools is an untrusted caller, not just an inconvenient one.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>agentengineering</category>
      <category>softwareengineering</category>
      <category>observability</category>
    </item>
    <item>
      <title>I ran an MCP-gateway production-readiness audit on a popular open-source LLM gateway. Here's what it found.</title>
      <dc:creator>Willian Pinho</dc:creator>
      <pubDate>Tue, 30 Jun 2026 20:41:45 +0000</pubDate>
      <link>https://dev.to/willianpinho/i-ran-an-mcp-gateway-production-readiness-audit-on-a-popular-open-source-llm-gateway-heres-what-3b5d</link>
      <guid>https://dev.to/willianpinho/i-ran-an-mcp-gateway-production-readiness-audit-on-a-popular-open-source-llm-gateway-heres-what-3b5d</guid>
      <description>&lt;h1&gt;
  
  
  I ran an MCP-gateway production-readiness audit on a popular open-source LLM gateway. Here's what it found.
&lt;/h1&gt;

&lt;p&gt;Most teams wiring an LLM gateway to MCP tools ask one question: does it work? The harder question, the one that decides whether you sleep through the launch, is different. When the authorization check throws an unexpected exception, does the gateway deny the call or allow it? That single line of behavior separates a mature platform from an incident waiting for a quiet Tuesday.&lt;/p&gt;

&lt;p&gt;So I built a structured way to answer it, and pointed it at a target that would not flatter the method.&lt;/p&gt;

&lt;h2&gt;
  
  
  The method, not the target
&lt;/h2&gt;

&lt;p&gt;The audit is a read-only, evidence-backed review across seven dimensions: tool-access governance and RBAC, fail-close versus fail-open behavior, MCP and agent onboarding, observability and tracing, multi-LLM routing and cost controls, secrets and identity, and broader production-readiness. Every finding has to point at the specific code that justifies it, pinned to a commit, so the team can open the file and read the same lines I did. There's no live fault injection and no guesswork; every claim traces back to code at that revision. If a control is present in code but only takes effect when an operator turns it on, that gets recorded too, because the default is what ships to most deployments.&lt;/p&gt;

&lt;p&gt;For a worked example, I used &lt;a href="https://github.com/BerriAI/litellm" rel="noopener noreferrer"&gt;LiteLLM&lt;/a&gt; from BerriAI, pinned at a specific commit. It is a widely deployed open-source LLM proxy, the code is public, and it is the kind of mature project that would expose a sloppy methodology rather than a sloppy target. I want to be clear about the result up front: it scored well. Four green, three yellow, zero red across the seven dimensions. The verdict was "production-ready with caveats." This is a fair assessment of a capable platform, not a takedown. The interesting part is that even a strong gateway has edges a structured pass will surface, and those edges are exactly the things every team running MCP in production should check on their own deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it gets right
&lt;/h2&gt;

&lt;p&gt;The dimensions that carry the most safety weight were the strongest.&lt;/p&gt;

&lt;p&gt;Identity and secrets came back green with no significant gaps. There were no inline secret values in the configuration. Real config references the environment via &lt;code&gt;os.environ&lt;/code&gt; and &lt;code&gt;os.getenv&lt;/code&gt;, and the only &lt;code&gt;sk-&lt;/code&gt; style strings in the tree were docstring examples. Identity is JWT and OIDC enforced on the actual gateway call path, not merely the dashboard login, and the end-user identity propagates through to MCP handling and spend logs instead of collapsing into one shared service credential. For MCP tokens, the gateway supports RFC 8693 OAuth token-exchange with audience and scope binding, so the MCP server receives a token minted for it rather than a forwarded user token. That follows the resource-server pattern the current MCP specification points at. One honest detail: it is an operator-enabled mode, not the default, so it counts as a control you have to turn on rather than one you inherit.&lt;/p&gt;

&lt;p&gt;Observability was also green. OpenTelemetry is a first-class integration with dedicated GenAI semantic-convention mapping, so per-model token and cost attribution is possible rather than bolted on later. Inbound W3C &lt;code&gt;traceparent&lt;/code&gt; headers are extracted through the standard propagator, which means end-to-end trace continuity is achievable across hops.&lt;/p&gt;

&lt;p&gt;Routing and cost, the dimension LiteLLM is purpose-built for, held up. A declarative &lt;code&gt;model_list&lt;/code&gt; maps virtual model names to physical deployments, and budget caps are genuinely enforced. An overrun raises a &lt;code&gt;BudgetExceededError&lt;/code&gt; rather than firing an alert and letting the spend continue. Rate limits in requests and tokens per minute are expressible per key, per model, and even per MCP server. That is a real, enforced path against bill-shock and denial-of-wallet, not a dashboard that turns red after the money is already gone.&lt;/p&gt;

&lt;p&gt;One honest caveat on observability: because this was a static review against a public repo with no live backend, I could not pull a real end-to-end trace and watch it reconstruct. The building blocks are present and standards-aligned. Confirming one real request stitched together end to end is a step any team should run against their own staging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three yellows worth checking on any deployment
&lt;/h2&gt;

&lt;p&gt;None of these are missing controls. They are default-configuration and operational gaps, which is the typical profile of a capable platform that needs hardening rather than rearchitecture.&lt;/p&gt;

&lt;p&gt;First, one fail-open line. Every per-level permission resolver fails closed: on an unexpected exception it logs and returns an empty set, which resolves to "no access" downstream. That is the correct posture. The exception is the top-level wrapper &lt;code&gt;get_allowed_mcp_servers()&lt;/code&gt;, which returns the allow-all server set on an unexpected error instead of an empty list. The blast radius is bounded to servers an operator already marked as public, but fail-open in an authorization resolver is the single highest-risk class in the whole framework, because a degraded check silently becomes "allow." It is also a one-line fix plus a regression test, which gives it the best risk-reduction-per-effort in the audit.&lt;/p&gt;

&lt;p&gt;Second, unpinned third-party MCP servers. The curated catalog launches stdio servers with floating commands like &lt;code&gt;npx -y @sentry/mcp-server&lt;/code&gt;, with no version, digest, or checksum pin. That is a tampered-package away from a supply-chain incident, the class OWASP labels LLM03 in its 2025 list. Pinning by version and digest, and rejecting anything unpinned, closes it.&lt;/p&gt;

&lt;p&gt;Third, per-tool least-privilege is opt-in. Authorization at the gateway is strong: it is enforced on caller identity as a strict intersection of key, team, end-user, agent, and org permissions, and the model is kept out of the authorization decision entirely, which is what defeats prompt-injection-to-tool-call. But absent a per-server &lt;code&gt;allowed_tools&lt;/code&gt; allowlist, any caller with access to a server can invoke any tool on it, including write or external ones — the excessive-agency exposure OWASP tracks as LLM06. Making that allowlist required at onboarding, validated in CI, converts least-privilege from opt-in to default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;A structured audit is not a search for a smoking gun. On a mature target there usually is not one, and there was not here. What the seven-dimension pass surfaced instead were the production-readiness edges that hide in a good codebase. One resolver that errs toward exposure where its siblings err toward safety. A supply chain that floats instead of pinning, and a least-privilege control that waits for someone to opt in. None of these show up when you ask "does it work," because the gateway works fine. They show up when you ask what happens at the boundaries, under error, and at the defaults most operators never change.&lt;/p&gt;

&lt;p&gt;That is the value of the method. It turns "we think it's fine" into a scored picture with named fixes, effort estimates, and the exact code behind each finding, so the team is arguing about a specific line of behavior instead of a feeling.&lt;/p&gt;

&lt;p&gt;The audit kit that produced this runs read-only and works in Claude Code, Cursor, or any MCP client. If you are putting an MCP gateway in front of production tools and want the same scored, evidence-backed pass on your own deployment, that is the scoped engagement I run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/BerriAI/litellm/tree/51ba6e39cd23576b9c2110361f1045782762f3e4" rel="noopener noreferrer"&gt;LiteLLM (BerriAI), pinned commit &lt;code&gt;51ba6e39cd23576b9c2110361f1045782762f3e4&lt;/code&gt;&lt;/a&gt; — the exact source audited; every finding points at code at this revision, so it can be read back at the same lines.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc8693" rel="noopener noreferrer"&gt;RFC 8693 — OAuth 2.0 Token Exchange&lt;/a&gt; — the token-exchange model behind audience- and scope-bound MCP tokens.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/specification/2025-06-18" rel="noopener noreferrer"&gt;Model Context Protocol specification (2025-06-18)&lt;/a&gt; — the resource-server model the gateway's MCP token design follows.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://genai.owasp.org/llm-top-10/" rel="noopener noreferrer"&gt;OWASP Top 10 for LLM Applications (2025)&lt;/a&gt; — LLM03 (Supply Chain) and LLM06 (Excessive Agency) frame the unpinned-server and least-privilege findings.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;W3C Trace Context&lt;/a&gt; — the &lt;code&gt;traceparent&lt;/code&gt; propagation standard that enables end-to-end trace continuity.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/open-telemetry/semantic-conventions-genai" rel="noopener noreferrer"&gt;OpenTelemetry GenAI semantic conventions&lt;/a&gt; — the per-model token and cost attribution mapping the gateway integrates.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>llm</category>
      <category>ai</category>
      <category>security</category>
    </item>
    <item>
      <title>Pre-mortem as plan debugger</title>
      <dc:creator>Willian Pinho</dc:creator>
      <pubDate>Tue, 23 Jun 2026 17:11:55 +0000</pubDate>
      <link>https://dev.to/willianpinho/pre-mortem-as-plan-debugger-37pk</link>
      <guid>https://dev.to/willianpinho/pre-mortem-as-plan-debugger-37pk</guid>
      <description>&lt;h2&gt;
  
  
  The ritual most senior engineers skip
&lt;/h2&gt;

&lt;p&gt;Everyone in this field has heard the word "pre-mortem." Almost nobody runs one as an actual habit.&lt;/p&gt;

&lt;p&gt;The people who do ship cleaner work. The cost is small: about 10 minutes to generate the failure list, 25 to 30 for the whole ritual. What it buys you is avoiding the moment on a post-incident call where someone says "yeah, we knew this could happen and shipped anyway." Plenty of Sev-1s aren't preventable. For the ones that are, this is the cheapest way I know to catch them early.&lt;/p&gt;

&lt;p&gt;What follows is the version I actually use: the mental flip that makes it work, the 10-minute structure, the three ways you can respond to each risk, and the cases where running a pre-mortem is just procrastination wearing a lab coat.&lt;/p&gt;

&lt;p&gt;One thing to hold onto if you skip the rest: the useful work happens in past tense, before the incident, in concrete sentences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why risk registers do not work
&lt;/h2&gt;

&lt;p&gt;A risk register is a passive list. It gets filed somewhere nobody opens, written in language nobody acts on.&lt;/p&gt;

&lt;p&gt;It tells you "what could go wrong" without telling you what to do about any of it. And the entries are written in future-conditional, "might happen," "could cause," which your brain quietly files under low-probability, deal-with-it-later. You read the list, you feel covered, you did the responsible thing. You wrote the risks down.&lt;/p&gt;

&lt;p&gt;The other problem is structural. The shipping plan and the register usually live in two different documents, so the register never forces anything back into the plan. It sits next to the work instead of changing it.&lt;/p&gt;

&lt;p&gt;After a decade-plus of shipping, here's my honest read: most risk registers get filled in the afternoon before launch to satisfy a checkbox. Nobody who writes one believes in it, and nobody reads it afterward. Which is fine, because usually nobody reads it at all.&lt;/p&gt;

&lt;p&gt;A pre-mortem has the opposite shape. It's active, it reads like a story, and it lives inside the plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cognitive shift: past tense as forcing function
&lt;/h2&gt;

&lt;p&gt;A pre-mortem is a story you write in the wrong direction. You write the post-incident retrospective before the incident, in past tense, with detail.&lt;/p&gt;

&lt;p&gt;The past tense is what forces specificity. There's a study on prospective hindsight (Mitchell, Russo and Pennington, 1989) that found imagining an outcome has already happened produces roughly 25% more concrete reasoning than predicting it forward. That's the lever the pre-mortem pulls. It's also why "we missed the deadline" is useless and "row-level locks piled up under the signup spike and transactions queued past the 30-second timeout" is not.&lt;/p&gt;

&lt;p&gt;Put the two side by side. The lazy version is "it broke." The useful version: the 30-second timeout cascaded into a 502 wall by minute three of the signup spike, conversion dropped 40% at peak hour because the payment provider rate-limited us, and the alerting system went quiet for 18 minutes because the alerting stack was its own alert sink.&lt;/p&gt;

&lt;p&gt;The second version you can actually test. Every clause points at a specific failure mode you can prevent, shrink, or at least watch for.&lt;/p&gt;

&lt;p&gt;This isn't pessimism dressed up. It's negative thinking pointed at the plan, run like a debugging session. You're not predicting the future. You're trying to break the document while it's still cheap to break.&lt;/p&gt;

&lt;p&gt;I ran one last week on a SaaS I'm shipping. Ten failure modes in ten minutes. Ranked by impact times likelihood, the top three were the lock-contention story, the payment rate-limit story, and the silent-alerting story above. Each one got a countermeasure before ship. But the bigger payoff was sequencing: the pre-mortem killed two days of work, because writing down how the wrong order failed showed me a feature I'd put first should have been third. You only see the right sequence after you've described the wrong one falling over.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10-minute protocol
&lt;/h2&gt;

&lt;p&gt;The "10 minutes" is the generation core, not the whole thing. Steps 1 and 2 eat the ten minutes; steps 3 through 5 add another 15 to 20. So the full ritual is closer to half an hour. Most teams stop after step 2, which is the whole reason pre-mortems get a reputation for not working. The discipline is in finishing.&lt;/p&gt;

&lt;p&gt;The original method (Klein's) is a group exercise: five to ten minutes of imagined-failure writing, then you read them round-robin, no fixed count. What I describe below is an opinionated solo adaptation for ship-level calls. The thing I actually run on a Monday morning before a non-trivial deploy.&lt;/p&gt;

&lt;p&gt;Solo, you only catch the failures you can already picture. So for anything launch-scale (new market, new product line, architecture you can't easily walk back), run it as a 30-minute group exercise with anonymous submission. That way the most junior person on the team can write down the failure mode the founder is too close to see. The brainstorming research is fairly brutal about face-to-face group ideation; anonymous parallel generation tends to beat it by 20 to 40%.&lt;/p&gt;

&lt;p&gt;The five steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set a timer for 10 minutes.&lt;/strong&gt; The time-box is what stops you polishing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write 10 specific past-tense failure narratives.&lt;/strong&gt; Concrete, not categories. Vendor outages, traffic shapes, a regulatory surprise, a key person out sick, a third-party API quietly changing its contract. Under 6 and you're still being abstract; over 12 and you're padding. Ten is the right squeeze for a 10-minute clock: one a minute, no editing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank each one by impact times likelihood.&lt;/strong&gt; Three buckets: ship-killing, recoverable, and acceptable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The top 3 get countermeasures in the plan itself.&lt;/strong&gt; Not in a separate doc. In the plan, as real tasks with an owner and a date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The rest go in a deferred-risk file&lt;/strong&gt; that you actually re-read at the next milestone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The protocol is deliberately dumb. The hard part is running it before every ship that takes more than a day to roll back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Countermeasure design: three patterns
&lt;/h2&gt;

&lt;p&gt;For each of the top three, you pick exactly one response. Not two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eliminate
&lt;/h3&gt;

&lt;p&gt;Change the plan so the failure mode can't happen. This is the most expensive option and the most durable. For the lock-contention case, what landed was moving primary-key allocation off a sequence and onto UUIDv7 on the signup-burst path, running READ COMMITTED. The failure mode doesn't get smaller. It's gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attenuate
&lt;/h3&gt;

&lt;p&gt;Shrink the blast radius for when it does happen. Cheaper, and partial. If the alerting stack can go dark, put an external uptime probe on a different vendor in a different region. The alerting system can still die; you just hear about it in 60 seconds instead of 30 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instrument
&lt;/h3&gt;

&lt;p&gt;Accept the failure mode but make sure you detect it in minutes, not hours. Cheapest of the three, and it leans entirely on someone being around to act on the signal. If you can't design the lock contention out before ship, then instrument lock-wait-time histograms and transaction-queue depth, alert on the 99th percentile, and accept that you'll respond in ten minutes rather than never having the bug.&lt;/p&gt;

&lt;p&gt;There's a fourth pattern in the textbooks: transfer, meaning you push the risk onto a vendor SLA, insurance, or a contract clause. I leave it out on purpose. Mid-launch, transfer is usually not yours to pull; when it is, procurement moves in weeks, not inside a 10-minute loop. ISO 31000 and PMBOK keep it on the list. The version that survives contact with a Monday deploy doesn't.&lt;/p&gt;

&lt;p&gt;Most launches end up using all three across their top-3 failures. The discipline is one pattern per failure. The moment you let yourself pick two, the countermeasure list bloats and the plan turns back into theater.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to run a pre-mortem
&lt;/h2&gt;

&lt;p&gt;Skip it on low-stakes reversible stuff. A one-line PR, a typo, a copy tweak on an internal page. The ten minutes won't earn themselves back.&lt;/p&gt;

&lt;p&gt;Skip it on calls you've already argued into the ground, where the choice is genuinely a coin flip. Running a pre-mortem on a coin flip is just a way to avoid deciding.&lt;/p&gt;

&lt;p&gt;And skip it on anything you've already shipped. That's a post-mortem: a different ritual, with different head-space and different stakes.&lt;/p&gt;

&lt;p&gt;The line I use: run a pre-mortem on anything that takes more than a day to roll back if it goes sideways. Under that bar, the ritual costs more than it saves.&lt;/p&gt;

&lt;p&gt;One honest limit. A pre-mortem only reaches as far as your imagination. The real incident is very often the eleventh failure mode, the one nobody wrote down. Run them anyway; catching seven of eight ordinary failures still beats catching zero. Just don't let a complete-looking list trick you into thinking you've seen the whole risk picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing rule
&lt;/h2&gt;

&lt;p&gt;If you can't describe how your next ship fails in 10 specific past-tense sentences, you haven't planned it. You've hoped. And hope has never held up under peak-hour traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Klein, G. (2007). "Performing a Project Premortem." Harvard Business Review, September 2007 — &lt;a href="https://hbr.org/2007/09/performing-a-project-premortem" rel="noopener noreferrer"&gt;https://hbr.org/2007/09/performing-a-project-premortem&lt;/a&gt; (canonical origin of the method)&lt;/li&gt;
&lt;li&gt;Mitchell, D. J., Russo, J. E., &amp;amp; Pennington, N. (1989). "Back to the future: Temporal perspective in the explanation of events." Journal of Behavioral Decision Making, 2(1), 25-38 — &lt;a href="https://doi.org/10.1002/bdm.3960020103" rel="noopener noreferrer"&gt;https://doi.org/10.1002/bdm.3960020103&lt;/a&gt; (empirical foundation for "past-tense forces specificity"; the prospective hindsight study)&lt;/li&gt;
&lt;li&gt;Kahneman, D. (2011). Thinking, Fast and Slow. Chapter 24, on pre-mortem as antidote to optimism bias and planning fallacy&lt;/li&gt;
&lt;li&gt;ISO 31000:2018 — Risk management guidelines — &lt;a href="https://www.iso.org/standard/65694.html" rel="noopener noreferrer"&gt;https://www.iso.org/standard/65694.html&lt;/a&gt; (canonical risk-treatment framework; eliminate/attenuate/instrument is an engineering simplification of clauses 6.5.2-6.5.3)&lt;/li&gt;
&lt;li&gt;PMBOK Guide, 7th Edition (PMI, 2021) — &lt;a href="https://www.pmi.org/standards/pmbok" rel="noopener noreferrer"&gt;https://www.pmi.org/standards/pmbok&lt;/a&gt; (alternative risk-response taxonomy: escalate / avoid / transfer / mitigate / accept)&lt;/li&gt;
&lt;li&gt;Klein, G. (2009). Streetlights and Shadows: Searching for the Keys to Adaptive Decision Making. MIT Press — &lt;a href="https://mitpress.mit.edu/9780262013390/streetlights-and-shadows/" rel="noopener noreferrer"&gt;https://mitpress.mit.edu/9780262013390/streetlights-and-shadows/&lt;/a&gt; (extended Klein corpus on naturalistic decision-making and pre-mortem limits)&lt;/li&gt;
&lt;li&gt;Mullen, B., Johnson, C., &amp;amp; Salas, E. (1991). "Productivity loss in brainstorming groups: A meta-analytic integration." Basic and Applied Social Psychology, 12(1), 3-23 — &lt;a href="https://doi.org/10.1207/s15324834basp1201_1" rel="noopener noreferrer"&gt;https://doi.org/10.1207/s15324834basp1201_1&lt;/a&gt; (why anonymous parallel generation outperforms face-to-face group brainstorming)&lt;/li&gt;
&lt;li&gt;Bezos, J. — Type 1 vs Type 2 decisions, 2016 Amazon shareholder letter (restating the 2015 framing) — &lt;a href="https://www.aboutamazon.com/news/company-news/2016-letter-to-shareholders" rel="noopener noreferrer"&gt;https://www.aboutamazon.com/news/company-news/2016-letter-to-shareholders&lt;/a&gt; (conceptual ancestor of the "one day to roll back" threshold)&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>engineering</category>
      <category>planning</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>The agent runtime event model</title>
      <dc:creator>Willian Pinho</dc:creator>
      <pubDate>Fri, 19 Jun 2026 13:43:29 +0000</pubDate>
      <link>https://dev.to/willianpinho/the-agent-runtime-event-model-55l9</link>
      <guid>https://dev.to/willianpinho/the-agent-runtime-event-model-55l9</guid>
      <description>&lt;h2&gt;
  
  
  The framework wars are a distraction
&lt;/h2&gt;

&lt;p&gt;Engineers keep asking me which agent framework to pick. LangChain or CrewAI or roll-your-own. It's the wrong question to lead with. The architectural choice that actually compounds isn't framework selection. It's where you can attach policy inside your agent runtime.&lt;/p&gt;

&lt;p&gt;Every production agent stack I've shipped over the last eighteen months converged on the same answer: four runtime events, four policy-attachment surfaces, and the hook coverage at each event is what separates a demo from something a regulated buyer will sign for.&lt;/p&gt;

&lt;p&gt;Here is the taxonomy, the bug class each event prevents, and a three-question audit you can run on any agent stack this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an event model is
&lt;/h2&gt;

&lt;p&gt;Every production agent runtime worth shipping exposes a small set of policy-attachment events. The taxonomy below comes from Claude Code's hook model, a developer-side agent runtime I work in daily: &lt;code&gt;UserPromptSubmit&lt;/code&gt;, &lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;, &lt;code&gt;Stop&lt;/code&gt;. I've adopted the same four-event model in the server-side agent stack my SaaS runs, mapped onto its runtime. The names are Claude Code's; the architectural pattern is what's portable. LangChain calls the analogous surfaces &lt;code&gt;on_chat_model_start&lt;/code&gt; / &lt;code&gt;on_tool_start&lt;/code&gt; / &lt;code&gt;on_tool_end&lt;/code&gt; / &lt;code&gt;on_agent_finish&lt;/code&gt;; OpenAI's Agents SDK calls them &lt;code&gt;on_agent_start&lt;/code&gt; / &lt;code&gt;on_tool_start&lt;/code&gt; / &lt;code&gt;on_tool_end&lt;/code&gt; / &lt;code&gt;on_agent_end&lt;/code&gt;. The names differ. The attachment surfaces, and the bugs they prevent, are the same.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UserPromptSubmit&lt;/code&gt; — before the prompt reaches the model.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PreToolUse&lt;/code&gt; — before the agent's chosen tool fires.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PostToolUse&lt;/code&gt; — after the tool returns, before the model sees the output.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Stop&lt;/code&gt; — after the agent says "done".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each event is a policy-attachment surface with its own semantics, and the differences are what you design around. The latency budget varies: &lt;code&gt;UserPromptSubmit&lt;/code&gt; must run in single-digit milliseconds because it sits on the user's input path, while &lt;code&gt;Stop&lt;/code&gt; can take a full second because the user has already submitted and is reading. That gap is why the input-path events can't run a synchronous deep check — you fail fast there and save the real verification for &lt;code&gt;Stop&lt;/code&gt;, where you can afford it. The failure modes differ too when a hook blocks. So does the data shape: the prompt event sees raw user text, the pre-tool event sees the planned tool invocation, the post-tool event sees the tool's return payload, and the stop event sees the full transcript.&lt;/p&gt;

&lt;p&gt;The architectural question isn't "which framework". It's "which of these four events does my runtime actually expose, and what policy coverage do I have at each one?".&lt;/p&gt;

&lt;p&gt;It sits closer to an aspect-oriented interceptor than to Express middleware, though neither maps cleanly. Middleware composes around a fixed request-response shape; an AOP interceptor wraps one method call. A hook attaches at a named lifecycle event defined by the runtime, the runtime's dispatcher decides when and whether you fire, the payload shape is event-specific, and your only contract is exit code and mutated payload.&lt;/p&gt;

&lt;p&gt;One thing the framework cannot decide for you: what happens when the hook itself crashes. PII redactors and secret-leak blockers must fail closed, better a denied request than a leaked credential. Completeness verifiers should fail open when they gate a plain response, a crashed classifier should not block the user from getting one. The exception proves the rule: when "done" gates a destructive action like a migration or a deploy, fail closed and alert instead. The hook's failure mode is policy, not infrastructure. Get this wrong and you ship either a security hole or a denial-of-service.&lt;/p&gt;

&lt;h2&gt;
  
  
  What class of bug each event prevents
&lt;/h2&gt;

&lt;h3&gt;
  
  
  UserPromptSubmit
&lt;/h3&gt;

&lt;p&gt;This event hard-blocks secrets in prompts using pattern matching plus entropy scoring for unknown formats, with provider verification on the high-value patterns (Stripe, AWS, GitHub) when latency budget allows. A canary token seeded in your secret stores catches the rare leak that slips both: if that token ever shows up in a submitted prompt, a real credential took the same path. It tokenizes PII into reversible placeholders so the model reasons over &lt;code&gt;⟪PII_email_42⟫&lt;/code&gt; instead of the real address. The receipt: a class of credential leak that slipped past code review three times in one week on a SaaS I'm shipping stopped being possible once this was wired in. The hook returns exit 2 and the prompt never reaches the provider. Same leak, caught at the runtime instead of in a review someone has to remember to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  PreToolUse
&lt;/h3&gt;

&lt;p&gt;Cost-budget enforcement at tool-call granularity: per-day, per-task, per-tool. Blocklist enforcement on file reads (no &lt;code&gt;.env*&lt;/code&gt;, no credential files, no rendered config with secrets baked in). Tool whitelisting in adversarial or untrusted-input contexts. This is the event that turns "the agent can do anything" into "the agent can do what its policy allows". One caveat worth stating plainly: this is a policy boundary, not an isolation boundary. For genuinely untrusted code execution you still want OS-level sandboxing (gVisor, Firecracker) underneath — the hook decides what's allowed, the sandbox contains what goes wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostToolUse
&lt;/h3&gt;

&lt;p&gt;PII redaction on stdout before the model ingests the tool output. A compliance audit log with a hash chain you can hand to a security reviewer. Injection-scan: tool outputs that look like prompt injections get stripped before they re-enter the conversation, which closes a class of indirect prompt injection that pure system-prompt defense cannot reach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop
&lt;/h3&gt;

&lt;p&gt;Completeness verification. A small classifier reads the user's items and asks whether each one got addressed. In our deployment, after about two weeks of tuning, the false-positive rate settled near 8% — so we treat it as a soft warning, not a hard block. The math is forgiving: a small classifier runs at roughly $0.001/turn (Haiku pricing, ~500 input tokens), so catching even one in twenty "I'll do that next" slop patterns covers the cost many times over. Test re-run gating also lives here, before the agent declares done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this beats framework selection
&lt;/h2&gt;

&lt;p&gt;Hooks are framework-agnostic. The contract is the runtime event, not the framework API. Swap one framework for another and the policy logic you wired in still applies — you rewrite the thin integration glue that extracts each runtime's payload, not the policies themselves. That's a fraction of a full re-architecture.&lt;/p&gt;

&lt;p&gt;The economic argument is sharper. Framework choice is a decision you'll revisit as the ecosystem churns — new entrants, better abstractions, a migration every couple of years. Hook coverage outlives all of that, because the four events stay put even as the policies you attach to them keep growing.&lt;/p&gt;

&lt;p&gt;OWASP's first LLM Top 10 in 2023 already flagged this, and the 2025 update sharpened it. Four of the ten items — Prompt Injection (LLM01), Sensitive Information Disclosure (LLM02), Improper Output Handling (LLM05), Excessive Agency (LLM06) — are runtime-control problems. None of them is a framework-selection problem. The conversation moved one layer down, to where you can actually attach policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  A three-question audit
&lt;/h2&gt;

&lt;p&gt;Three questions for any production agent stack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which event does your secret-leak prevention attach to? If none, you are shipping with a known leak surface every team has crossed at least once. (OWASP LLM02: Sensitive Information Disclosure.)&lt;/li&gt;
&lt;li&gt;Which event catches completion lies, agent says done when it is not? If none, user trust erodes invisibly until churn spikes and you cannot trace why. (No OWASP entry yet. This is the gap the standard has not caught up to.)&lt;/li&gt;
&lt;li&gt;Which event audits tool outputs for PII before the model reingests them? If none, regulated industries cannot deploy your stack regardless of how impressive the demo is. (OWASP LLM02 + LLM05: Improper Output Handling.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Frameworks are a commodity. Your hook coverage is the moat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Claude Code Hooks reference — &lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;https://code.claude.com/docs/en/hooks&lt;/a&gt; (authoritative event surface, payload shape, exit-code semantics)&lt;/li&gt;
&lt;li&gt;LangChain Callbacks (BaseCallbackHandler) — &lt;a href="https://reference.langchain.com/python/langchain-core" rel="noopener noreferrer"&gt;https://reference.langchain.com/python/langchain-core&lt;/a&gt; (cross-framework mapping)&lt;/li&gt;
&lt;li&gt;OWASP Top 10 for LLM Applications 2025 — &lt;a href="https://genai.owasp.org/llm-top-10/" rel="noopener noreferrer"&gt;https://genai.owasp.org/llm-top-10/&lt;/a&gt; (LLM01 / LLM02 / LLM05 / LLM06 mapping to runtime events)&lt;/li&gt;
&lt;li&gt;NIST AI Risk Management Framework — &lt;a href="https://www.nist.gov/itl/ai-risk-management-framework" rel="noopener noreferrer"&gt;https://www.nist.gov/itl/ai-risk-management-framework&lt;/a&gt; (the Generative AI Profile, NIST-AI-600-1, July 2024 — its govern/map/measure/manage functions sit at the organizational and lifecycle level; these runtime events are one place those controls get operationalized, not where NIST defines them)&lt;/li&gt;
&lt;li&gt;Microsoft Presidio — Anonymizer — &lt;a href="https://microsoft.github.io/presidio/anonymizer/" rel="noopener noreferrer"&gt;https://microsoft.github.io/presidio/anonymizer/&lt;/a&gt; (reversible PII tokenization pattern)&lt;/li&gt;
&lt;li&gt;Trufflehog — verifier-based secret detection — &lt;a href="https://github.com/trufflesecurity/trufflehog" rel="noopener noreferrer"&gt;https://github.com/trufflesecurity/trufflehog&lt;/a&gt; (2026 industry standard for credential scanning)&lt;/li&gt;
&lt;li&gt;Greshake et al., "Not what you've signed up for: Compromising Real-World LLM-Integrated Applications with Indirect Prompt Injection" — &lt;a href="https://arxiv.org/abs/2302.12173" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2302.12173&lt;/a&gt; (academic foundation for the &lt;code&gt;PostToolUse&lt;/code&gt; injection-scan claim)&lt;/li&gt;
&lt;li&gt;Anthropic Claude pricing — &lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;https://platform.claude.com/docs/en/about-claude/pricing&lt;/a&gt; (verifies the ~$0.001/turn cost number)&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>I made Claude, GPT and Gemini predict the entire 2026 World Cup. Here's the experiment design.</title>
      <dc:creator>Willian Pinho</dc:creator>
      <pubDate>Thu, 11 Jun 2026 17:53:13 +0000</pubDate>
      <link>https://dev.to/willianpinho/i-made-claude-gpt-and-gemini-predict-the-entire-2026-world-cup-heres-the-experiment-design-2nm1</link>
      <guid>https://dev.to/willianpinho/i-made-claude-gpt-and-gemini-predict-the-entire-2026-world-cup-heres-the-experiment-design-2nm1</guid>
      <description>&lt;p&gt;The 2026 World Cup kicks off today: 48 teams, 104 matches, five weeks. I'm using it as a benchmark.&lt;/p&gt;

&lt;p&gt;Three frontier models (Claude Opus 4.8, GPT-5.2 and Gemini 3.1 Pro) predicted every group match with scorelines and win/draw/loss probabilities, then a complete knockout bracket down to the champion and Golden Boot. Every prediction was locked before kickoff and committed to a public repo. As real results arrive, a live site scores them automatically.&lt;/p&gt;

&lt;p&gt;Building it surfaced some genuinely weird model behavior. GPT-5.2, for instance, kept inventing an impossible football rule until the prompt explicitly forbade it. More on that below.&lt;/p&gt;

&lt;p&gt;But the core of the project is the experiment design: the picks matter less than &lt;em&gt;how&lt;/em&gt; the question was asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The confound nobody controls for
&lt;/h2&gt;

&lt;p&gt;Ask an LLM with web access to predict a match and you have no idea what you measured. You can't tell whether it reasoned from internal knowledge, scraped a reliable source, or hallucinated a plausible-sounding stat. Two models citing different injury reports aren't comparable. A friend reviewing the project put it bluntly: without a standardized source, the models can just make that information up.&lt;/p&gt;

&lt;p&gt;So each model runs under three conditions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;What it isolates&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;web&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chat/CLI with live web access&lt;/td&gt;
&lt;td&gt;Model + free-form sourcing (uncontrolled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;baseline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API, no tools, no extra context&lt;/td&gt;
&lt;td&gt;Pure parametric knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;enriched&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API, no tools, + identical data snapshot&lt;/td&gt;
&lt;td&gt;Reasoning over controlled inputs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The enriched snapshot is the same for all three models: the official FIFA ranking (April 2026 release, pulled from FIFA's own API) and World Football Elo Ratings for all 48 teams, versioned in the repo with sources and retrieval dates. No model gets an information advantage.&lt;/p&gt;

&lt;p&gt;If enriched beats baseline, the value was in the data. If baseline holds its own, the knowledge was already in the weights. The web arm tells us whether free browsing helps or just adds noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the no-tools arms honest
&lt;/h2&gt;

&lt;p&gt;"No tools" is an instruction until you verify it. The API arms run through a LiteLLM gateway with no tool definitions. Gemini's runs go through its CLI, which reports per-call tool stats, and the runner rejects any response where the tool-call counter isn't zero (same pattern for Claude's bracket runs, with all tools disallowed). It's verified per request, not assumed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toolCalls&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requireNoTools&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCalls&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Arm violation, not a model error: the no-tools condition must hold. Retry raw.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;MAX_RETRIES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Group &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;gf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: transport used &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; tool call(s) — no-tools arm violated`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs are strict JSON, validated with Zod against the official fixture list — every response must contain exactly the six expected team pairs for its group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PredictionItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;teamA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;teamB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;scoreA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;scoreB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;probWinA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;probDraw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;probWinB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invalid responses get the validation errors fed back, up to three attempts.&lt;/p&gt;

&lt;p&gt;Now, the weird behavior I promised. GPT-5.2 consistently labeled knockout ties that were level after extra time as "decided in extra time", which is impossible under the rules, and validation feedback alone didn't fix it. It only stopped when the prompt spelled out: a level score after 120 minutes means penalties. Claude had its own quirk: the schema documented the winner field as &lt;code&gt;"winner": "&amp;lt;teamA|teamB&amp;gt;"&lt;/code&gt;, and it returned the literal string &lt;code&gt;"teamA"&lt;/code&gt;. Prompt precision beats prompt length.&lt;/p&gt;

&lt;h2&gt;
  
  
  What they predicted
&lt;/h2&gt;

&lt;p&gt;The headline disagreement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; picks &lt;strong&gt;Spain&lt;/strong&gt; as champion in all three arms (the only model that's consistent with itself).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt; says &lt;strong&gt;Brazil&lt;/strong&gt; (web and baseline) but switches to &lt;strong&gt;France&lt;/strong&gt; when given the standardized data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT-5.2&lt;/strong&gt; says &lt;strong&gt;Brazil&lt;/strong&gt; on the web arm, &lt;strong&gt;France&lt;/strong&gt; on both API arms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three picked &lt;strong&gt;Mbappé&lt;/strong&gt; for the Golden Boot in 7 of the 9 brackets.&lt;/p&gt;

&lt;p&gt;That inconsistency across arms is itself a result: the same model gives a different answer depending on what information it was handed. The tournament will tell us which configuration was actually calibrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoring
&lt;/h2&gt;

&lt;p&gt;Group stage, per match: 5 points for the exact score, 3 for the correct result plus one exact side, 2 for the result only. On top of that, a multiclass &lt;strong&gt;Brier score&lt;/strong&gt; over the win/draw/loss probabilities, which heavily penalizes overconfident wrong predictions. That matters more to me than raw hit rate.&lt;/p&gt;

&lt;p&gt;Brackets are scored pool-style: 1/2/4/8/16 points per team correctly placed in the real round of 32, 16, quarters, semis and final, plus 32 for the champion. Max 192.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The API arms receive the fixture list (the web arm had to recall the official draw), so they test judgement on outcomes, not memory of the schedule.&lt;/li&gt;
&lt;li&gt;Group tiebreakers are simplified; fair-play points can't be reproduced from scorelines.&lt;/li&gt;
&lt;li&gt;Player-level predictions (scorers, cards) are out of scope: adding them after kickoff would break the locked-before-the-tournament guarantee.&lt;/li&gt;
&lt;li&gt;One tournament is one sample. This measures calibration on a single event, not "which model is smarter."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stack, briefly
&lt;/h2&gt;

&lt;p&gt;Next.js 16 (App Router) + Prisma 7 + SQLite, results sync from openfootball every 30 minutes, 56 unit tests on the pure scoring/standings/validation logic. The real bracket renders with official placeholder slots ("Group A runner-up", "Winner of match 73") and fills itself in as the tournament progresses.&lt;/p&gt;

&lt;p&gt;Everything — prompts, raw model JSON, dataset, runner scripts, scoring code — is in the repo. If you think the methodology is flawed, the receipts are right there to prove it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live leaderboard:&lt;/strong&gt; &lt;a href="https://worldcup2026.willianpinho.com" rel="noopener noreferrer"&gt;https://worldcup2026.willianpinho.com&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/willianpinho/worldcup-predictor-2026" rel="noopener noreferrer"&gt;https://github.com/willianpinho/worldcup-predictor-2026&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll publish the group-stage verdict on 28 June and a full post-mortem after the final on 19 July. Place your (intellectual) bets now: does the model that knows the most football win, or the one that's best calibrated about what it doesn't know?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Independent project — not affiliated with Anthropic, Google or OpenAI. Educational experiment, not betting advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I wrote a read-only scanner for MCP / agent-gateway production-readiness</title>
      <dc:creator>Willian Pinho</dc:creator>
      <pubDate>Tue, 09 Jun 2026 23:20:10 +0000</pubDate>
      <link>https://dev.to/willianpinho/i-wrote-a-read-only-scanner-for-mcp-agent-gateway-production-readiness-4jbh</link>
      <guid>https://dev.to/willianpinho/i-wrote-a-read-only-scanner-for-mcp-agent-gateway-production-readiness-4jbh</guid>
      <description>&lt;p&gt;I build and maintain an MCP server that works in Claude Code, Cursor, and Gemini CLI. Doing that for a while taught me something uncomfortable: the distance between a registered MCP server and full access to everything it exposes is roughly zero. The convenience is the exposure.&lt;/p&gt;

&lt;p&gt;So when teams move an agent from "works in the demo" to "touches real systems," the same gaps show up every time. Tool access gets granted per-server instead of per-operation. Fail-open becomes the default nobody chose on purpose. New servers show up by editing a config and restarting — no review gate, no pinned versions. Often nothing traces a call across agent → gateway → model → tool, and multi-model usage runs with no routing policy, which becomes a surprise bill at the end of the month.&lt;/p&gt;

&lt;p&gt;Most of that is visible by reading the repo. I got tired of reading it by hand, so I wrote &lt;code&gt;mcp-gateway-scan&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it checks
&lt;/h2&gt;

&lt;p&gt;It's a static, read-only scan across seven dimensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tool-access / RBAC&lt;/strong&gt; — are tools scoped per-operation against an identity, or does registering a server hand over its whole surface?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail-close&lt;/strong&gt; — when a model, gateway, or tool dependency degrades, does the system refuse safely or improvise?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding / supply-chain pinning&lt;/strong&gt; — is adding a server a reviewed, declarative change with pinned versions, or a config edit and a restart?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability / OTel&lt;/strong&gt; — is there end-to-end tracing you can reconstruct a call from?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-LLM routing &amp;amp; cost&lt;/strong&gt; — is there a routing policy, fallback behavior, and per-team cost attribution, or one hardcoded model and hope?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets / identity&lt;/strong&gt; — credentials in configs vs. references; is identity propagated to tool calls?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-readiness&lt;/strong&gt; — kill-switch, rate limits, eval gates, the operational table stakes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's read-only by design. It never executes your code and never prints a secret value. At most it points to a secret literal sitting inline in a config it shouldn't be — a location to check, not a verdict.&lt;/p&gt;

&lt;p&gt;The seven dimensions track failure modes the official MCP security guidance and the OWASP LLM Top 10 call out — over-broad tool access, fail-open agency, and unpinned supply chains among them (&lt;a href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices" rel="noopener noreferrer"&gt;MCP security best practices&lt;/a&gt;, &lt;a href="https://genai.owasp.org/llm-top-10/" rel="noopener noreferrer"&gt;OWASP LLM Top 10&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to run it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CLI, for a one-off read:&lt;/strong&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 mcp-gateway-scan ./your-repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CI gate, exits non-zero on any red:&lt;/strong&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 mcp-gateway-scan &lt;span class="nt"&gt;--ci&lt;/span&gt; ./your-repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;MCP server, so your agent can scan on request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add gateway-scan &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; mcp-gateway-scan mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ask Claude or Cursor "scan my gateway" and the agent calls the tool itself. The thing that scans agent stacks is, fittingly, an agent tool.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mcp-gateway-scan ./your-repo

  Tool-access / RBAC ............ yellow  tools registered per-server; no per-operation grants
  Fail-close .................... red     no fallback policy; gateway errors return last cached response
  Onboarding / supply-chain ..... green   declarative config, versions pinned
  Observability / OTel .......... yellow  request logging present; no trace propagation to tool calls
  Multi-LLM routing &amp;amp; cost ...... green   routing policy + per-team attribution found
  Secrets / identity ............ red     API key inline in gateway.config.json
  Production-readiness .......... yellow  rate limits present; no kill-switch

  2 red · 3 yellow · 2 green
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reds are the ones I'd fix before the next deploy. Yellows are "you have the foundation, it's not wired through." Greens mean the pattern is actually in place, not just intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring the CI gate
&lt;/h2&gt;

&lt;p&gt;For most teams, the &lt;code&gt;--ci&lt;/code&gt; flag is where this earns its keep. Drop it in a workflow and a new red blocks the merge:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gateway readiness gate&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 mcp-gateway-scan --ci ${{ github.workspace }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Non-zero exit on red, zero otherwise. You can ratchet: ship today with yellows allowed, then tighten the gate to fail on yellow once you've cleared the reds. The scanner is fast and has no network dependency, so it's cheap to run on every PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;It's a first read, not a verdict. A static scan can tell you there's no fallback policy in the config; it can't tell you whether your fail-close behavior actually holds under load, because that needs fault injection against a running system. It reads what you declared, not what happens at 3 a.m. when a provider rate-limits you.&lt;/p&gt;

&lt;p&gt;For calibration I took the seven-dimension methodology to &lt;a href="https://github.com/BerriAI/litellm" rel="noopener noreferrer"&gt;LiteLLM&lt;/a&gt;, a mature and widely-deployed proxy, pinned at a commit. It came back 4 green / 3 yellow / 0 red — production-ready, with the usual edges a structured pass surfaces in a good codebase: one authorization resolver that fails open where its siblings fail closed, unpinned third-party MCP servers, and per-tool least-privilege left opt-in. Then I built a deliberately-broken reference gateway that fails the same checks by construction. The scanner here automates the static slice of that methodology — the part you can run in seconds, before any deeper review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Run it: &lt;code&gt;npx mcp-gateway-scan ./your-repo&lt;/code&gt; — MIT, &lt;a href="https://github.com/willianpinho/mcp-gateway-scan" rel="noopener noreferrer"&gt;github.com/willianpinho/mcp-gateway-scan&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;code&gt;mcp-gateway-scan&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want the deeper version, I run a full &lt;strong&gt;Provenwright MCP Gateway Readiness Audit&lt;/strong&gt; (live fault-injection, trace verification, a 90-day remediation roadmap) at &lt;a href="https://willianpinho.com/mcp-audit?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=mcp-gateway-scan-launch" rel="noopener noreferrer"&gt;willianpinho.com/mcp-audit&lt;/a&gt;. The free scanner stands on its own; that's just there if you want the rest.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Large File MCP: Handle Massive Files in Claude with Intelligent Chunking</title>
      <dc:creator>Willian Pinho</dc:creator>
      <pubDate>Tue, 11 Nov 2025 03:58:03 +0000</pubDate>
      <link>https://dev.to/willianpinho/large-file-mcp-handle-massive-files-in-claude-with-intelligent-chunking-56fh</link>
      <guid>https://dev.to/willianpinho/large-file-mcp-handle-massive-files-in-claude-with-intelligent-chunking-56fh</guid>
      <description>&lt;p&gt;@ -0,0 +1,325 @@&lt;/p&gt;

&lt;h2&gt;
  
  
  Large File MCP: Handle Massive Files in Claude with Intelligent Chunking
&lt;/h2&gt;

&lt;p&gt;Have you ever tried to analyze a 500MB log file with Claude only to hit token limits? Or struggled to navigate through a massive CSV dataset? I built &lt;a href="https://github.com/willianpinho/large-file-mcp" rel="noopener noreferrer"&gt;Large File MCP&lt;/a&gt; to solve exactly these problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Large Files in AI Assistants
&lt;/h2&gt;

&lt;p&gt;AI assistants like Claude Desktop are incredibly powerful, but they have a fundamental limitation: token context windows. When you're dealing with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-gigabyte log files from production servers&lt;/li&gt;
&lt;li&gt;Large CSV datasets with millions of rows&lt;/li&gt;
&lt;li&gt;Massive JSON configuration files&lt;/li&gt;
&lt;li&gt;Extensive codebases spanning thousands of lines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...traditional file reading approaches fail. You can't just load everything into memory, and manually chunking files is tedious and error-prone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Large File MCP
&lt;/h2&gt;

&lt;p&gt;Large File MCP is a &lt;strong&gt;Model Context Protocol (MCP) server&lt;/strong&gt; that provides intelligent, production-ready large file handling for AI assistants. It's designed to make working with files of any size as seamless as working with small text files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Smart Chunking&lt;/strong&gt;&lt;br&gt;
The server automatically detects your file type and applies optimal chunking strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text/log files: 500 lines per chunk&lt;/li&gt;
&lt;li&gt;Code files (.ts, .py, .java): 300 lines per chunk&lt;/li&gt;
&lt;li&gt;CSV files: 1000 lines per chunk&lt;/li&gt;
&lt;li&gt;JSON files: 100 lines per chunk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Intelligent Navigation&lt;/strong&gt;&lt;br&gt;
Jump to any line in a file with surrounding context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me line 1234 of /var/log/system.log with context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Powerful Search&lt;/strong&gt;&lt;br&gt;
Find patterns with regex support and contextual results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find all ERROR messages in /var/log/app.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Memory Efficient&lt;/strong&gt;&lt;br&gt;
Files are streamed line-by-line, never fully loaded into memory. Built-in LRU caching provides 80-90% hit rates for frequently accessed files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production Ready&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;91.8% test coverage&lt;/li&gt;
&lt;li&gt;Cross-platform (Windows, macOS, Linux)&lt;/li&gt;
&lt;li&gt;Type-safe TypeScript implementation&lt;/li&gt;
&lt;li&gt;Comprehensive documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Installing Large File MCP is straightforward. Choose your preferred method:&lt;/p&gt;
&lt;h3&gt;
  
  
  Claude Desktop (Recommended)
&lt;/h3&gt;

&lt;p&gt;Add to your &lt;code&gt;claude_desktop_config.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"large-file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@willianpinho/large-file-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Config file locations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS: &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Windows: &lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Restart Claude Desktop after editing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add globally for all projects&lt;/span&gt;
claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; stdio &lt;span class="nt"&gt;--scope&lt;/span&gt; user large-file-mcp &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; @willianpinho/large-file-mcp

&lt;span class="c"&gt;# Verify installation&lt;/span&gt;
claude mcp list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  npm Global Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @willianpinho/large-file-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;Let me show you how Large File MCP transforms common workflows:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Analyzing Production Logs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; You need to debug a production issue buried in a 2GB nginx log file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Large File MCP:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find all 500 errors in /var/log/nginx/access.log from the last hour
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI assistant will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;search_in_large_file&lt;/code&gt; with regex pattern&lt;/li&gt;
&lt;li&gt;Return matching lines with context&lt;/li&gt;
&lt;li&gt;Stream results efficiently without loading the entire file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Found 47 errors in ~3 seconds, with full context for each match.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Code Navigation in Large Codebases
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Understanding function definitions in a 10,000-line Python file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Large File MCP:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find all class definitions in /project/src/main.py and explain their purpose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server uses intelligent chunking to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to each class definition&lt;/li&gt;
&lt;li&gt;Provide surrounding context&lt;/li&gt;
&lt;li&gt;Cache frequently accessed code sections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Instant navigation to any part of the codebase with full context.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. CSV Data Exploration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Analyzing a 500MB sales dataset with 5 million rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Large File MCP:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me the structure of /data/sales.csv and find all transactions over $10,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI uses:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;get_file_structure&lt;/code&gt; - Get metadata and sample rows&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;search_in_large_file&lt;/code&gt; - Find high-value transactions&lt;/li&gt;
&lt;li&gt;Smart chunking - Process 1000 rows at a time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Comprehensive analysis without loading 500MB into memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Streaming Very Large Files
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Processing a 5GB JSON dataset that exceeds memory limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Large File MCP:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream the first 100MB of /data/huge_dataset.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uses &lt;code&gt;stream_large_file&lt;/code&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configurable chunk sizes (default 64KB)&lt;/li&gt;
&lt;li&gt;Starting offset support&lt;/li&gt;
&lt;li&gt;Maximum chunk limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Progressive processing of massive files with minimal memory footprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Available Tools
&lt;/h2&gt;

&lt;p&gt;Large File MCP provides 6 powerful tools:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. read_large_file_chunk
&lt;/h3&gt;

&lt;p&gt;Read specific chunks with intelligent sizing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/log/system.log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chunkIndex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeLineNumbers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. search_in_large_file
&lt;/h3&gt;

&lt;p&gt;Regex search with context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/log/error.log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR.*database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"regex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contextBefore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contextAfter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. navigate_to_line
&lt;/h3&gt;

&lt;p&gt;Jump to specific lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/code/app.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contextLines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. get_file_structure
&lt;/h3&gt;

&lt;p&gt;Comprehensive file analysis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/data/sales.csv"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. get_file_summary
&lt;/h3&gt;

&lt;p&gt;Statistical summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/data/report.txt"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. stream_large_file
&lt;/h3&gt;

&lt;p&gt;Stream files progressively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/data/huge_file.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chunkSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;65536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxChunks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Benchmarks
&lt;/h2&gt;

&lt;p&gt;Here's how Large File MCP performs across different file sizes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File Size&lt;/th&gt;
&lt;th&gt;Operation Time&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt; 1MB&lt;/td&gt;
&lt;td&gt;&amp;lt; 100ms&lt;/td&gt;
&lt;td&gt;Direct read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1-100MB&lt;/td&gt;
&lt;td&gt;&amp;lt; 500ms&lt;/td&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100MB-1GB&lt;/td&gt;
&lt;td&gt;1-3s&lt;/td&gt;
&lt;td&gt;Streaming + cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt; 1GB&lt;/td&gt;
&lt;td&gt;Progressive&lt;/td&gt;
&lt;td&gt;AsyncGenerator&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Caching Performance:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LRU Cache: 100MB default size&lt;/li&gt;
&lt;li&gt;TTL: 5 minutes&lt;/li&gt;
&lt;li&gt;Cache hit rate: 80-90% for repeated access&lt;/li&gt;
&lt;li&gt;Significant speedup for frequently accessed files&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration &amp;amp; Customization
&lt;/h2&gt;

&lt;p&gt;Fine-tune behavior using environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"large-file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@willianpinho/large-file-mcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"CHUNK_SIZE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"CACHE_ENABLED"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"CACHE_SIZE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"209715200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MAX_FILE_SIZE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10737418240"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Available Options:&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;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CHUNK_SIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lines per chunk&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OVERLAP_LINES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chunk overlap&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MAX_FILE_SIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Max file size&lt;/td&gt;
&lt;td&gt;10GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CACHE_SIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cache size&lt;/td&gt;
&lt;td&gt;100MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CACHE_TTL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cache TTL&lt;/td&gt;
&lt;td&gt;5min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why MCP?
&lt;/h2&gt;

&lt;p&gt;The Model Context Protocol (MCP) is an open protocol that standardizes how AI assistants interact with external tools and data sources. Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Universal Compatibility:&lt;/strong&gt; Works with Claude Desktop, Claude Code CLI, and other MCP-compatible clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Sandboxed execution with explicit permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility:&lt;/strong&gt; Easy to integrate with other MCP servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardization:&lt;/strong&gt; One implementation works everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production-Ready Quality
&lt;/h2&gt;

&lt;p&gt;Large File MCP is built to production standards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;91.8% Test Coverage&lt;/strong&gt; - Comprehensive test suite with Jest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety&lt;/strong&gt; - Written in TypeScript with strict typing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD&lt;/strong&gt; - Automated testing and deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; - Complete docs at &lt;a href="https://willianpinho.github.io/large-file-mcp/" rel="noopener noreferrer"&gt;willianpinho.github.io/large-file-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active Maintenance&lt;/strong&gt; - Regular updates and bug fixes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Ready to handle large files effortlessly?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the server:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; stdio &lt;span class="nt"&gt;--scope&lt;/span&gt; user large-file-mcp &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; @willianpinho/large-file-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify installation:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   claude mcp list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start using it:&lt;/strong&gt;
Open Claude and try:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Analyze /var/log/system.log and find all errors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links &amp;amp; Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/willianpinho/large-file-mcp" rel="noopener noreferrer"&gt;github.com/willianpinho/large-file-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/@willianpinho/large-file-mcp" rel="noopener noreferrer"&gt;npmjs.com/package/@willianpinho/large-file-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://willianpinho.github.io/large-file-mcp/" rel="noopener noreferrer"&gt;willianpinho.github.io/large-file-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues:&lt;/strong&gt; &lt;a href="https://github.com/willianpinho/large-file-mcp/issues" rel="noopener noreferrer"&gt;github.com/willianpinho/large-file-mcp/issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Large File MCP transforms how you work with massive files in AI assistants. Whether you're debugging production logs, analyzing datasets, or navigating large codebases, intelligent chunking and streaming make it seamless.&lt;/p&gt;

&lt;p&gt;The combination of smart chunking, powerful search, efficient caching, and production-ready quality makes it an essential tool for developers working with large files.&lt;/p&gt;

&lt;p&gt;Give it a try and let me know what you think! Star the project on GitHub if you find it useful.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What large file challenges are you facing?&lt;/strong&gt; Share your use cases in the comments!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>tooling</category>
      <category>opensource</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
