<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>ToolMesh | Blog</title><description/><link>https://www.toolmesh.io/</link><language>en</language><item><title>OWASP MCP Top 10: What a Gateway Fixes — and What It Doesn&apos;t</title><link>https://www.toolmesh.io/en/blog/owasp-mcp-top-10/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/owasp-mcp-top-10/</guid><description>The OWASP MCP Top 10 (v0.1, beta) names ten risk categories for Model Context Protocol deployments. Mapped against a gateway&apos;s enforcement pipeline, not one of them closes fully: some attack paths disappear by construction, others depend on a default that ships switched off, and MCP06 sits outside the boundary. With the exposure figures from arXiv 2608.00150, the NSA AISC design considerations, and the MCP06 title discrepancy on OWASP&apos;s own page.

</description><pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;A gateway eliminates some OWASP MCP Top 10 attack paths by construction, puts others behind a switch you still have to throw, and does not reach one category at all.&lt;/strong&gt; That is the honest short answer, and this post is the long one: each of the ten categories, what a control plane in front of your MCP estate actually does about it, and where the mechanism stops. The reference implementation throughout is &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh&quot;&gt;ToolMesh&lt;/a&gt;, our Apache-2.0 self-hosted MCP gateway, so every claim below is checkable against code you can read.&lt;/p&gt;
&lt;p&gt;Three things you will find here that a summary of the list does not give you: the measured exposure figures the list responds to, a category-by-category table with a verdict per row, and a section naming the categories a gateway leaves open — anchored in someone else’s analysis, not our own marketing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-numbers-that-made-this-list-urgent&quot;&gt;The numbers that made this list urgent&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Top 10 did not arrive in a vacuum — and it is older than this summer’s headlines: the list was there before the measurement that made it urgent. In July 2026, Nicolás Padilla published &lt;a href=&quot;https://arxiv.org/abs/2608.00150&quot;&gt;&lt;em&gt;Exposed by Design: A Dynamic Security Assessment of Internet-Facing MCP Servers at Scale&lt;/em&gt;&lt;/a&gt; (arXiv:2608.00150, v1 submitted 31 July 2026), the first dynamic behavioural assessment of MCP servers reachable from the public internet. Its findings, taken from the paper’s own abstract:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;over 21,000&lt;/strong&gt; MCP server instances detectable on the public internet&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;640&lt;/strong&gt; production servers confirmed, of which &lt;strong&gt;414&lt;/strong&gt; were dynamically audited&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;91.8 %&lt;/strong&gt; of the dynamically audited servers lack OAuth authentication&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;687 tool instances&lt;/strong&gt; across the confirmed servers expose shell execution capabilities without access controls&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;41.6 %&lt;/strong&gt; of confirmed servers disappear within three days between measurement runs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last number is the one operators should sit with. It describes an estate deployed faster than it can be reviewed. &lt;a href=&quot;https://forkast.news/the-model-context-protocol-reaches-a-security-inflection-point/&quot;&gt;Forkast summarised the wider picture on 11 August 2026&lt;/a&gt;, counting more than ten critical or high-severity CVEs in the MCP ecosystem over the same period. One detail worth carrying if you compare write-ups: the 91.8 % applies to the 414 dynamically audited servers, and the 687 shell-capable instances are &lt;em&gt;tools&lt;/em&gt;, not servers — several secondary reports round both onto the 640-server figure.&lt;/p&gt;
&lt;p&gt;Public-sector guidance landed earlier. On 20 May 2026 the NSA’s Artificial Intelligence Security Center released the Cybersecurity Information Sheet &lt;a href=&quot;https://www.nsa.gov/Press-Room/Press-Releases-Statements/Press-Release-View/Article/4496698/nsa-releases-security-design-considerations-for-ai-driven-automation-leveraging/&quot;&gt;&lt;em&gt;Model Context Protocol (MCP): Security Design Considerations for AI-Driven Automation&lt;/em&gt;&lt;/a&gt;. Its framing matters more than any single recommendation: alongside serialization risks and trust boundaries, the CSI names &lt;em&gt;dynamic tool invocation&lt;/em&gt;, &lt;em&gt;implicit trust relationships&lt;/em&gt;, and &lt;em&gt;context sharing&lt;/em&gt; as risks that established cyber defence strategies do not adequately address, and argues these cannot be patched at the interface or endpoint level.&lt;/p&gt;
&lt;p&gt;That is a structural claim, and it is why a per-server fix does not scale. Twenty-one thousand individually hardened servers is not a plan.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-owasp-mcp-top-10-actually-is&quot;&gt;What the OWASP MCP Top 10 actually is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Precision about the artefact’s status is part of using it honestly. The &lt;a href=&quot;https://owasp.org/www-project-mcp-top-10/&quot;&gt;OWASP Top 10 for Model Context Protocol&lt;/a&gt; is an &lt;strong&gt;Incubator project&lt;/strong&gt;, currently at &lt;strong&gt;version v0.1&lt;/strong&gt;, and its own roadmap places it in &lt;strong&gt;Phase 3 — Beta Release and Pilot Testing&lt;/strong&gt;, with the next release scheduled for &lt;strong&gt;October 2026&lt;/strong&gt; (project page, checked 18 August 2026). The document is licensed CC BY-NC-SA 4.0.&lt;/p&gt;
&lt;p&gt;So: not a certification target, not a compliance baseline, and the IDs still carry the &lt;code dir=&quot;auto&quot;&gt;:2025&lt;/code&gt; suffix. What it &lt;em&gt;is&lt;/em&gt; is a shared vocabulary for MCP risk, which is exactly what the category was missing. Treat it as a checklist for design review rather than an audit standard — and expect the categories to move in October.&lt;/p&gt;
&lt;p&gt;The ten categories, as given in the summary list on the project page:&lt;/p&gt;

















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;ID&lt;/th&gt;&lt;th&gt;Category&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;MCP01:2025&lt;/td&gt;&lt;td&gt;Token Mismanagement &amp;#x26; Secret Exposure&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP02:2025&lt;/td&gt;&lt;td&gt;Privilege Escalation via Scope Creep&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP03:2025&lt;/td&gt;&lt;td&gt;Tool Poisoning&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP04:2025&lt;/td&gt;&lt;td&gt;Software Supply Chain Attacks &amp;#x26; Dependency Tampering&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP05:2025&lt;/td&gt;&lt;td&gt;Command Injection &amp;#x26; Execution&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP06:2025&lt;/td&gt;&lt;td&gt;Intent Flow Subversion&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP07:2025&lt;/td&gt;&lt;td&gt;Insufficient Authentication &amp;#x26; Authorization&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP08:2025&lt;/td&gt;&lt;td&gt;Lack of Audit and Telemetry&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP09:2025&lt;/td&gt;&lt;td&gt;Shadow MCP Servers&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP10:2025&lt;/td&gt;&lt;td&gt;Context Injection &amp;#x26; Over-Sharing&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;One caveat about that table, and it is the sharpest available evidence for the beta status above: &lt;strong&gt;the project page does not agree with itself.&lt;/strong&gt; The summary list gives MCP06 as &lt;em&gt;Intent Flow Subversion&lt;/em&gt;. A second, fuller listing further down the same page titles the same entry &lt;em&gt;Prompt Injection via Contextual Payloads&lt;/em&gt; — and in that listing the IDs lose their zero padding (&lt;code dir=&quot;auto&quot;&gt;MCP1:2025&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MCP4:2025&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MCP5:2025&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MCP6:2025&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MCP8:2025&lt;/code&gt;) while MCP03 and MCP07 keep theirs. Both readings were live on the page on 18 August 2026. We use the summary list throughout this post. If you cite OWASP MCP06 anywhere else, say which of the two titles you mean, because they describe noticeably different things: one is about hijacked intent, the other about prompt injection as an injection class.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-mapping-at-a-glance&quot;&gt;The mapping at a glance&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh runs every tool call through one pipeline. The source comment states the order plainly: &lt;em&gt;AuthZ → Credentials → Gate pre → Backend → Gate post → Audit&lt;/em&gt; (&lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh/blob/main/internal/executor/executor.go&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;internal/executor/executor.go&lt;/code&gt;&lt;/a&gt;). The mapping below is that pipeline held against the ten categories, with a verdict per row.&lt;/p&gt;




























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;OWASP category&lt;/th&gt;&lt;th&gt;Mechanism&lt;/th&gt;&lt;th&gt;Verdict&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;MCP01 Token Mismanagement &amp;#x26; Secret Exposure&lt;/td&gt;&lt;td&gt;Credentials injected server-side per request; never in client config or model context&lt;/td&gt;&lt;td&gt;Client/model exposure closed; credential lifecycle open&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP02 Privilege Escalation via Scope Creep&lt;/td&gt;&lt;td&gt;OpenFGA relationship check per call; discovery filtered by the same check&lt;/td&gt;&lt;td&gt;Enforced when configured&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP03 Tool Poisoning&lt;/td&gt;&lt;td&gt;REST tool definitions come from operator-held YAML, not from the upstream server&lt;/td&gt;&lt;td&gt;Closed for DADL backends, open for aggregated MCP servers&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP04 Supply Chain Attacks &amp;#x26; Dependency Tampering&lt;/td&gt;&lt;td&gt;A backend is ~30 lines of YAML, not a package; strict parser rejects unknown fields&lt;/td&gt;&lt;td&gt;Reduced, not eliminated&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP05 Command Injection &amp;#x26; Execution&lt;/td&gt;&lt;td&gt;Tools are declared HTTP calls with typed parameters; no shell unless you add one&lt;/td&gt;&lt;td&gt;Shell injection closed at the tool layer; backend injection remains&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP06 Intent Flow Subversion&lt;/td&gt;&lt;td&gt;Blast-radius limits only: authorization ceiling, output policies, audit trail&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Not closed&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP07 Insufficient Authentication &amp;#x26; Authorization&lt;/td&gt;&lt;td&gt;OAuth 2.1 issuer, PKCE mandatory, S256 only; bcrypt API keys&lt;/td&gt;&lt;td&gt;Authentication closed; authorization only when enforcement is on&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP08 Lack of Audit and Telemetry&lt;/td&gt;&lt;td&gt;Structured audit entry on every exit path, denials included&lt;/td&gt;&lt;td&gt;Recorded everywhere; not immutable, and the write itself is fail-open&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP09 Shadow MCP Servers&lt;/td&gt;&lt;td&gt;One endpoint, one file-declared catalog — an inventory by construction&lt;/td&gt;&lt;td&gt;Consolidated, not prevented&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MCP10 Context Injection &amp;#x26; Over-Sharing&lt;/td&gt;&lt;td&gt;goja policy engine pre and post execution; Code Mode projects results&lt;/td&gt;&lt;td&gt;Enforced when configured&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;the-ten-categories-one-by-one&quot;&gt;The ten categories, one by one&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp012025--token-mismanagement--secret-exposure&quot;&gt;MCP01:2025 — Token Mismanagement &amp;#x26; Secret Exposure&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The failure mode is a backend API key sitting in a client config file, or worse, passing through the model’s context on its way to a tool. In ToolMesh, backend credentials never reach the client. They resolve inside &lt;code dir=&quot;auto&quot;&gt;ExecuteTool()&lt;/code&gt; at step 2 of the pipeline and are attached to the outbound request by the adapter — the agent asks for &lt;code dir=&quot;auto&quot;&gt;netbox_list_devices&lt;/code&gt;, not for a token. Revoking a key touches the gateway, not thirty client configurations.&lt;/p&gt;
&lt;p&gt;Honest scope, and it is wider than it first looks. The shipped credential store is the embedded one, reading &lt;code dir=&quot;auto&quot;&gt;CREDENTIAL_*&lt;/code&gt; environment variables; Vault, OpenBao and Infisical adapters are documented as the tiering path, but the open-source binary today ships the embedded store, and per-user credential resolution is an accepted-design ADR rather than running code. More importantly, OWASP’s own scope for MCP01 goes past exposure: its description names long-lived tokens explicitly and calls short-lived, scoped credentials essential. The &lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/MCP_Security_Cheat_Sheet.html&quot;&gt;OWASP MCP Security Cheat Sheet&lt;/a&gt; is blunter still — do not store secrets in server code, configs, or environment variables, and prefer ephemeral tokens to long-lived ones. Read against that, ToolMesh closes the path from credential to model and leaves credential lifecycle where it found it.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp022025--privilege-escalation-via-scope-creep&quot;&gt;MCP02:2025 — Privilege Escalation via Scope Creep&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Authorization runs first in the pipeline, per call and per user, against an &lt;a href=&quot;https://openfga.dev/&quot;&gt;OpenFGA&lt;/a&gt; relationship model in a User → Plan → Tool structure. The detail worth knowing: the &lt;em&gt;same&lt;/em&gt; check filters tool discovery, so a user who may not call a tool does not see it in the listing either. Scope-to-filter injection goes one step further and narrows backend query parameters from the caller’s scope, so authorization shapes the request rather than only permitting it.&lt;/p&gt;
&lt;p&gt;Honest scope: &lt;strong&gt;ToolMesh ships with &lt;code dir=&quot;auto&quot;&gt;OPENFGA_MODE=bypass&lt;/code&gt; as the default&lt;/strong&gt; — no authorization checks at all. Enforcement is one setting (&lt;code dir=&quot;auto&quot;&gt;restrict&lt;/code&gt;) plus a relationship model you have to write, and a gateway configured for convenience enforces nothing. The model is documented under &lt;a href=&quot;https://www.toolmesh.io/en/authorization/&quot;&gt;Authorization&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp032025--tool-poisoning&quot;&gt;MCP03:2025 — Tool Poisoning&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The rug-pull variant of this attack works because an MCP client re-reads tool definitions from the server, so a description approved in review can change afterwards. For backends declared in &lt;a href=&quot;https://www.toolmesh.io/en/dadl/&quot;&gt;DADL&lt;/a&gt;, that channel does not exist: &lt;code dir=&quot;auto&quot;&gt;RESTAdapter.ListTools&lt;/code&gt; reads the parsed YAML file the operator holds — it takes no context and makes no upstream call. The catalog changes when someone edits a file in your repository, which is a reviewable event with an author attached.&lt;/p&gt;
&lt;p&gt;Honest scope: this holds for DADL-declared REST backends. Where ToolMesh aggregates a genuine upstream MCP server, &lt;code dir=&quot;auto&quot;&gt;MCPAdapter.ListTools&lt;/code&gt; does query that server, and those definitions are exactly as trustworthy as their source. In neither case does the gateway semantically inspect a description for hidden instructions — pinning is not detection.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp042025--software-supply-chain-attacks--dependency-tampering&quot;&gt;MCP04:2025 — Software Supply Chain Attacks &amp;#x26; Dependency Tampering&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every MCP server you add is normally a package, a runtime, and a transitive dependency tree. That cost model is the starting point of our own preprint, &lt;a href=&quot;https://arxiv.org/abs/2605.05247&quot;&gt;&lt;em&gt;DADL: A Declarative Description Language for Enterprise Tool Libraries in LLM Agent Systems&lt;/em&gt;&lt;/a&gt; (arXiv:2605.05247, 4 May 2026): at organizational scale every API integration ships as a dedicated server process with its own deployment, dependency tree and credential handling — while the overwhelming majority of those servers turn out to be thin wrappers around REST APIs. With DADL, adding an API means adding a YAML file: no third-party code enters the runtime. The parser is explicit about what it does not understand, which is a weaker and more useful property than strictness: an unknown key is re-decoded strictly, reported as a warning and then ignored — the DADL spec’s warn-and-ignore rule, so a file using newer additive features still loads, visibly degraded rather than silently. Two things do fail closed: a &lt;code dir=&quot;auto&quot;&gt;requires&lt;/code&gt; block naming a capability this build lacks refuses the load outright, and a behaviour-determining value this version does not implement — a pagination behaviour, a stream handling — is rejected rather than guessed.&lt;/p&gt;
&lt;p&gt;Honest scope: this &lt;em&gt;shifts&lt;/em&gt; the supply chain rather than removing it. The gateway binary has its own Go dependency tree, and a malicious DADL file is still a config-supply-chain problem — the mitigation is that it is reviewable YAML in git rather than minified code in &lt;code dir=&quot;auto&quot;&gt;node_modules&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp052025--command-injection--execution&quot;&gt;MCP05:2025 — Command Injection &amp;#x26; Execution&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Set against the paper’s 687 tool instances with unrestricted shell execution, this is the most structural difference in the list. A DADL tool is a declared HTTP call: method, path, and a typed parameter schema. There is no shell to inject into, because there is no shell — one appears only if an operator deliberately adds a backend that exposes one. Outbound URL validation fails closed on DNS resolution errors and treats unresolvable addresses as private, which is the SSRF-to-cloud-metadata path the same paper reports finding in the wild.&lt;/p&gt;
&lt;p&gt;Honest scope: OWASP’s category is broader than shells. Its description covers an agent constructing and executing system commands, shell scripts, &lt;em&gt;API calls&lt;/em&gt;, or code snippets from untrusted input without validation — and constructing API calls is exactly what DADL does for a living. A typed string parameter stops the tool layer from becoming an interpreter; it does not stop the backend on the far end from passing that string into its own SQL or shell. If the backend API itself exposes a command-execution endpoint, DADL will expose it faithfully. Declarative binding constrains the transport, not the API you point it at, and input validation against the backend’s semantics remains yours to write — in the gate, if nowhere else. The MCP03 split carries over unchanged, too: aggregate an upstream MCP server that ships a shell tool, and the gateway passes that tool through — subject to authorization and the gates like any other tool, but “no shell” is a property of the DADL layer, not a filter imposed on foreign catalogs.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp062025--intent-flow-subversion&quot;&gt;MCP06:2025 — Intent Flow Subversion&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This is the category a gateway does not close, and pretending otherwise would discredit the other nine. When injected context steers an agent toward an attacker’s goal, the resulting tool call is authenticated, authorized, schema-valid, and wrong. Every check in the pipeline passes, because every check answers &lt;em&gt;whether this caller may do this thing&lt;/em&gt; — not &lt;em&gt;why they are asking&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;What the pipeline does contribute is a ceiling and a record: authorization bounds what a subverted agent can reach at all, output policies can block specific exfiltration shapes, and the audit log makes the trajectory reconstructable afterwards. Semantic authorization — an LLM supervisor reasoning about intent — exists in our concept notes and not in the binary. Until it does, treat MCP06 as unmitigated and design the blast radius accordingly.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp072025--insufficient-authentication--authorization&quot;&gt;MCP07:2025 — Insufficient Authentication &amp;#x26; Authorization&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Against the 91.8 % figure, the authentication half of this category has a sharp answer. ToolMesh acts as an OAuth 2.1 issuer: PKCE is mandatory, &lt;code dir=&quot;auto&quot;&gt;S256&lt;/code&gt; is the only accepted challenge method, and a request without &lt;code dir=&quot;auto&quot;&gt;code_challenge&lt;/code&gt; is rejected at the authorization endpoint. API keys and user passwords are bcrypt-hashed. No anonymous caller reaches a tool.&lt;/p&gt;
&lt;p&gt;Honest scope: the category is called &lt;em&gt;Authentication &lt;strong&gt;&amp;#x26;&lt;/strong&gt; Authorization&lt;/em&gt;, and the second word lands where MCP02 already left it — &lt;code dir=&quot;auto&quot;&gt;OPENFGA_MODE&lt;/code&gt; defaults to &lt;code dir=&quot;auto&quot;&gt;bypass&lt;/code&gt;, so out of the box every authenticated caller is also a fully authorized one. Saying the pipeline’s authorization step “passes” in that mode would be a word game: no authorization decision is taken at all. Strictly read, ToolMesh closes the authentication half of MCP07 by construction and hands you the authorization half as a configuration task. Beyond that, TLS terminates in front of the gateway (Caddy, nginx, or Cloudflare Tunnel), identity is local — &lt;code dir=&quot;auto&quot;&gt;users.yaml&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;apikeys.yaml&lt;/code&gt;, or the built-in issuer — and OIDC connectors to Keycloak, Entra, Okta or Zitadel are roadmap, not shipped. The current surface is documented under &lt;a href=&quot;https://www.toolmesh.io/en/authentication/&quot;&gt;Authentication&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp082025--lack-of-audit-and-telemetry&quot;&gt;MCP08:2025 — Lack of Audit and Telemetry&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;An audit entry is written on &lt;strong&gt;every&lt;/strong&gt; exit path of the pipeline, including the ones that never reach a backend: authorization error, authorization denial, pre-gate rejection, backend error, post-gate rejection, and success. Denials are the entries that matter during an incident, and they are the ones homegrown logging most often misses. Storage is structured &lt;code dir=&quot;auto&quot;&gt;slog&lt;/code&gt; to stdout or SQLite (&lt;code dir=&quot;auto&quot;&gt;AUDIT_STORE&lt;/code&gt;), with retention configurable and defaulting to 90 days; Prometheus metrics run alongside.&lt;/p&gt;
&lt;p&gt;Honest scope, in two parts. First, the log is trustworthy exactly as far as the host is: tamper-evidence — hash chaining with external anchoring — is an ADR draft, not an implementation, and OWASP asks for immutable audit trails while the Cheat Sheet adds SIEM ingestion and alerting on top. Neither is in the box. Second, and less obvious: &lt;strong&gt;the audit write is deliberately fail-open.&lt;/strong&gt; The source comment says so in as many words — audit recording failures do not block the tool response, because availability is prioritized over auditability. So the pipeline is fail-closed at the steps that decide whether a call happens, and fail-open at the step that records it. If your requirement is that no unlogged call may ever execute, ToolMesh does not meet it today; you would run an external audit pipeline and alert on the error entries.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp092025--shadow-mcp-servers&quot;&gt;MCP09:2025 — Shadow MCP Servers&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Consolidation is the whole answer here, and it is a deployment property rather than a clever feature. One endpoint, one catalog, declared in files: our own production instance currently fronts &lt;strong&gt;5,590 tools across 60 backends&lt;/strong&gt; behind a single MCP endpoint (checked 18 August 2026). Asking “what can our agents reach?” is reading a directory, not running a network scan.&lt;/p&gt;
&lt;p&gt;Honest scope: a gateway does not stop anyone from running an MCP server beside it. Preventing shadow deployments is egress policy, asset management and organisational discipline. What a gateway removes is the &lt;em&gt;reason&lt;/em&gt; to spin one up — and it gives you something to compare a scan against.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mcp102025--context-injection--over-sharing&quot;&gt;MCP10:2025 — Context Injection &amp;#x26; Over-Sharing&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.toolmesh.io/en/output-gate/&quot;&gt;Output Gate&lt;/a&gt; is a goja JavaScript policy engine that runs twice per call: pre-execution on the request, post-execution on the response, with a five-second timeout per policy. Shipped policies cover PII redaction, role-based field filtering and caller blocking; redaction itself fails closed. &lt;a href=&quot;https://www.toolmesh.io/en/code-mode/&quot;&gt;Code Mode&lt;/a&gt; attacks the same category from the other side — the agent runs typed JavaScript against the tools and returns only what it projects, so a thousand-row response can be reduced to five fields before anything enters the context window.&lt;/p&gt;
&lt;p&gt;Honest scope: the open-source gate is deterministic rules only. The LLM-based compliance evaluator is an enterprise extension, and human-in-the-loop review is not implemented at all. A rule catches what you wrote it to catch.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-a-gateway-does-not-solve&quot;&gt;What a gateway does not solve&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The most useful critique of the gateway pattern this year came from outside the vendor field. In &lt;a href=&quot;https://www.infoq.com/articles/securing-mcp-production-gateway/&quot;&gt;&lt;em&gt;Securing MCP in Production: Defense-in-Depth beyond the Gateway&lt;/em&gt;&lt;/a&gt; (InfoQ, 29 July 2026), Nik Kale grants that gateways deliver central authentication, authorization, auditing and policy evaluation — and then structures what still has to exist around them into four defense layers, each of which, he argues, has its earliest trustworthy enforcement point somewhere other than the gateway. His verdict on the pattern is two three-word sentences: &lt;em&gt;“It is necessary. It is insufficient.”&lt;/em&gt; We agree, and held against the map above his four layers split evenly: two land where DADL already changes the mechanics, two sit beyond anything a pipeline reaches.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Safe tool execution.&lt;/strong&gt; A gateway cannot ensure that a tool handler treats its arguments as data rather than instructions. For DADL-declared backends this layer collapses by construction: there is no handler code to get wrong, because the gateway itself builds the HTTP call from the typed schema. The MCP05 limits stay standing — what the backend does with the values, and every handler inside an aggregated MCP server, sit beyond it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The management plane.&lt;/strong&gt; The inspectors, harnesses, dashboards and consoles &lt;em&gt;around&lt;/em&gt; MCP are not behind the gateway, and they frequently hold more privilege than the agents do. Nothing in the pipeline above protects them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outbound trust, egress and token scope.&lt;/strong&gt; Server-side injection stops a credential from reaching the model. It does not stop a backend from being handed a token broader than the task needed, or from making its own unsafe outbound calls with it. Credential scoping is a decision you make when you write the DADL, and no pipeline stage rescues an over-broad token.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic integrity and manifest pinning.&lt;/strong&gt; Kale’s fourth layer is the rug-pull problem from MCP03, and he points out that the MCP specification is silent on pinning — functionality teams have to build at the gateway themselves. DADL-declared backends get it by construction; aggregated MCP servers do not. That is exactly where our MCP03 verdict splits.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One category from the table appears in none of the four layers: MCP06. A call that is authenticated, authorized and schema-valid, made for an attacker’s reasons, is not an execution problem, a management-plane problem, an egress problem or a manifest problem. Which is one more way of saying what the verdict column already does: a gateway does not close it, with or without the layers around it.&lt;/p&gt;
&lt;p&gt;We add one gap of our own, because it applies to us specifically: &lt;strong&gt;a gateway concentrates risk as much as it concentrates control.&lt;/strong&gt; Consolidating 60 backends behind one endpoint means that endpoint holds every credential and fronts every tool — an internet-facing MCP endpoint and a high-value target in one object. It needs its own hardening, its own exposure review, and a place in the estate you audit rather than only being the tool you audit with. We track that as an open item on our own instance, not as a solved problem.&lt;/p&gt;
&lt;p&gt;Two things are worth saying against the temptation to read all of this as an argument for doing nothing. First, the pattern is not speculative: in &lt;a href=&quot;https://arxiv.org/abs/2608.10760&quot;&gt;&lt;em&gt;A Gateway Architecture for Enterprise MCP Authentication&lt;/em&gt;&lt;/a&gt; (arXiv:2608.10760, v1, 11 August 2026), a team of authors with PayPal affiliations describes exactly this consolidation — per-team authentication implemented independently, no consistent way to authorize callers or offboard a departing employee — and reports an architecture in production fronting dozens of internal MCP servers. The paper itself does not name the operator; the affiliation is visible in the authors’ addresses. Independent of any vendor, the category is being validated at enterprise scale. Second, “insufficient” is not “useless” — and OWASP says as much from its own side. The &lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/MCP_Security_Cheat_Sheet.html&quot;&gt;MCP Security Cheat Sheet&lt;/a&gt; recommends using an MCP proxy or gateway to enforce isolation policies between servers, and pinning tool definitions to detect rug pulls. Those are two of the mechanisms described above, recommended by the same foundation that publishes the risk list. Every category except MCP06 gets materially better at the gateway — and gets better once, rather than separately on every server.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-toolmesh-stands&quot;&gt;Where ToolMesh stands&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh is an Apache-2.0, self-hosted MCP gateway written in Go that runs every tool call through one central enforcement pipeline — authorize, inject credentials, gate, execute, gate, audit — fail-closed at each enforcement step you have switched on, and, as noted under MCP08, fail-open at the audit write. Backends are declared in &lt;a href=&quot;https://www.toolmesh.io/en/dadl/&quot;&gt;DADL&lt;/a&gt;, YAML files you keep in git, which is why the tool catalog is a reviewable artefact rather than a runtime negotiation. The architecture behind the pipeline is laid out in &lt;a href=&quot;https://www.toolmesh.io/en/blog/anatomy-of-an-enterprise-tool-library/&quot;&gt;Anatomy of an Enterprise Tool Library&lt;/a&gt;; what the pattern looks like under the new stateless spec is in &lt;a href=&quot;https://www.toolmesh.io/en/blog/mcp-goes-stateless/&quot;&gt;MCP Goes Stateless&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Where we come out on the Top 10, read strictly: &lt;strong&gt;not one category is fully closed in OWASP’s own scope.&lt;/strong&gt; What a gateway does is narrower and more useful than a row of green ticks. It eliminates specific attack paths by construction — credentials reaching the model, an interpreter at the tool layer, rug-pulled tool definitions on DADL-declared backends. It puts several more behind one switch that has to be thrown, and &lt;code dir=&quot;auto&quot;&gt;bypass&lt;/code&gt; is the default. It improves the rest by consolidation rather than by mechanism. And MCP06 sits outside its boundary entirely. That is a defensible position for August 2026 and not a finished one. The OWASP list moves to its next release in October, and we would rather re-run this mapping against the new version than quietly let it age.&lt;/p&gt;
&lt;p&gt;If you run the same mapping against your own estate and reach a different verdict on any row, &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh/discussions&quot;&gt;GitHub Discussions&lt;/a&gt; is open — a disagreement about one of these ten is more useful to us than agreement about all of them.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;sources&quot;&gt;Sources&lt;/h2&gt;&lt;/div&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Source&lt;/th&gt;&lt;th&gt;Date&lt;/th&gt;&lt;th&gt;Used for&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://owasp.org/www-project-mcp-top-10/&quot;&gt;OWASP Top 10 for MCP&lt;/a&gt;&lt;/td&gt;&lt;td&gt;v0.1, checked 18 Aug 2026&lt;/td&gt;&lt;td&gt;Category IDs, titles and scope; project status; the MCP06 discrepancy&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/MCP_Security_Cheat_Sheet.html&quot;&gt;OWASP MCP Security Cheat Sheet&lt;/a&gt;&lt;/td&gt;&lt;td&gt;undated, checked 18 Aug 2026&lt;/td&gt;&lt;td&gt;Credential guidance, tool pinning, gateway isolation, SIEM&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://arxiv.org/abs/2608.00150&quot;&gt;arXiv:2608.00150 — &lt;em&gt;Exposed by Design&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;v1, 31 Jul 2026&lt;/td&gt;&lt;td&gt;21,000+ instances, 640/414 servers, 91.8 %, 687 tools, 41.6 %&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.nsa.gov/Press-Room/Press-Releases-Statements/Press-Release-View/Article/4496698/nsa-releases-security-design-considerations-for-ai-driven-automation-leveraging/&quot;&gt;NSA AISC Cybersecurity Information Sheet on MCP&lt;/a&gt;&lt;/td&gt;&lt;td&gt;20 May 2026&lt;/td&gt;&lt;td&gt;Dynamic tool invocation, implicit trust, context sharing&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://forkast.news/the-model-context-protocol-reaches-a-security-inflection-point/&quot;&gt;Forkast — MCP security inflection point&lt;/a&gt;&lt;/td&gt;&lt;td&gt;11 Aug 2026&lt;/td&gt;&lt;td&gt;10+ critical/high CVEs, ecosystem context (secondary source)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://www.infoq.com/articles/securing-mcp-production-gateway/&quot;&gt;InfoQ — &lt;em&gt;Defense-in-Depth beyond the Gateway&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;29 Jul 2026&lt;/td&gt;&lt;td&gt;The four defense layers beyond the gateway&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://arxiv.org/abs/2608.10760&quot;&gt;arXiv:2608.10760 — &lt;em&gt;A Gateway Architecture for Enterprise MCP Authentication&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;v1, 11 Aug 2026&lt;/td&gt;&lt;td&gt;Independent production validation of the pattern&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;</content:encoded><category>MCP</category><category>Security</category><category>Architecture</category></item><item><title>MCP Goes Stateless — What It Means for Your Gateway</title><link>https://www.toolmesh.io/en/blog/mcp-goes-stateless/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/mcp-goes-stateless/</guid><description>MCP 2026-07-28 makes the protocol stateless: sessions removed, routable headers required, and Sampling, Roots, and Logging deprecated on a twelve-month clock. What to check in your own MCP servers now, why the new spec reads as if it expects a gateway — and what a gateway honestly does not solve. Full change table with SEP references included.

</description><pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;MCP is stateless now — at its protocol core.&lt;/strong&gt; On July 28, 2026 the Model Context Protocol shipped &lt;a href=&quot;https://modelcontextprotocol.io/specification/2026-07-28/changelog&quot;&gt;2026-07-28&lt;/a&gt; — the revision its maintainers call the most important since remote MCP launched, and the start of a countdown. If you operate MCP servers, three capabilities your setup may quietly rely on — Sampling, Roots, and protocol Logging — are now deprecated, with removal possible after twelve months. Protocol sessions are gone entirely: every request now stands on its own.&lt;/p&gt;
&lt;p&gt;This post answers three operator questions: what actually changed, what to check in your own MCP estate while the clock runs, and why the new spec — read closely — assumes there is a gateway between your agents and your servers. The spec-level detail, every breaking change with its SEP reference, waits in the reference table at the end of this post.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-changed-in-plain-terms&quot;&gt;What changed, in plain terms&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://blog.modelcontextprotocol.io/posts/2026-07-28&quot;&gt;release post&lt;/a&gt; explains the pressure behind the revision: the maintainers report close to half a billion SDK downloads per month across the Tier 1 SDKs. At that scale a protocol inherits infrastructure problems, and MCP’s biggest one was the session. A client was pinned to whichever server instance held its session — load balancers needed sticky sessions, deployments had to drain them, and serverless platforms fought the protocol at every step.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://modelcontextprotocol.io/specification/2026-07-28/changelog&quot;&gt;2026-07-28 changelog&lt;/a&gt; answers with four moves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sessions and the &lt;code dir=&quot;auto&quot;&gt;initialize&lt;/code&gt; handshake are gone.&lt;/strong&gt; Version, identity, and capabilities travel inside each request, so any request can land on any instance behind a plain round-robin load balancer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two routing headers become mandatory.&lt;/strong&gt; Every Streamable HTTP request carries &lt;code dir=&quot;auto&quot;&gt;Mcp-Method&lt;/code&gt; (what is being called); tool calls also carry &lt;code dir=&quot;auto&quot;&gt;Mcp-Name&lt;/code&gt; (which tool). The point: infrastructure between client and server can route, rate-limit, and meter MCP traffic without opening the JSON body.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Servers no longer send their own JSON-RPC requests to clients.&lt;/strong&gt; The old server-initiated pattern behind Sampling, Elicitation, and Roots is replaced by Multi Round-Trip Requests (MRTR): the server answers “I need more input” (&lt;code dir=&quot;auto&quot;&gt;input_required&lt;/code&gt;), and the client retries the request with the answer attached. Notifications survive — request-scoped ones flow on the response stream, and one long-lived &lt;code dir=&quot;auto&quot;&gt;subscriptions/listen&lt;/code&gt; stream carries the change notifications a client opts in to. One misreading to avoid: &lt;strong&gt;Elicitation is not deprecated&lt;/strong&gt; — only its server-initiated form is gone; elicitation itself lives on inside MRTR.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deprecation becomes policy, not habit.&lt;/strong&gt; Roots, Sampling, and Logging keep working for at least twelve months, but new implementations should not adopt them. The new &lt;a href=&quot;https://modelcontextprotocol.io/community/feature-lifecycle&quot;&gt;feature lifecycle&lt;/a&gt; guarantees the window; only an actively exploited security risk can shorten it, and never below ninety days.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;sessions-are-gone--where-did-the-state-go&quot;&gt;Sessions are gone — where did the state go?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Stateless is a statement about the &lt;em&gt;protocol&lt;/em&gt;, not about your application. The changelog says where cross-call state goes now: servers that need it “use explicit, server-minted handles passed as ordinary tool arguments” (&lt;a href=&quot;https://modelcontextprotocol.io/specification/2026-07-28/changelog&quot;&gt;SEP-2567&lt;/a&gt;). Your database cursor, your SSH session, your half-finished workflow — none of that disappeared. It moved up a layer, out of the transport and into the application. Something up there now has to mint those handles, store what they point to, expire them, and decide who may use them.&lt;/p&gt;
&lt;p&gt;Hold that thought. First, the practical part.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;which-of-your-mcp-servers-use-deprecated-capabilities&quot;&gt;Which of your MCP servers use deprecated capabilities?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The twelve-month clock started on July 28. Whether you run three MCP servers or thirty, the transition is an audit before it is a migration:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Transports.&lt;/strong&gt; Which servers still speak the 2024-11-05 HTTP+SSE transport? It is formally deprecated; Streamable HTTP is the target.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sampling.&lt;/strong&gt; Which servers call &lt;code dir=&quot;auto&quot;&gt;sampling/createMessage&lt;/code&gt;? The documented migration is direct integration with an LLM provider API — a real architecture decision (whose key, whose budget, whose logs), not a search-and-replace.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Roots.&lt;/strong&gt; Which servers expect &lt;code dir=&quot;auto&quot;&gt;roots/list&lt;/code&gt;? Migration: pass directories and files as tool parameters, resource URIs, or server configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging.&lt;/strong&gt; Which servers rely on &lt;code dir=&quot;auto&quot;&gt;logging/setLevel&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;notifications/message&lt;/code&gt;? Note that &lt;code dir=&quot;auto&quot;&gt;logging/setLevel&lt;/code&gt; is removed outright in 2026-07-28 — log level moves into each request’s &lt;code dir=&quot;auto&quot;&gt;_meta&lt;/code&gt;. Migration: &lt;code dir=&quot;auto&quot;&gt;stderr&lt;/code&gt; on stdio, or OpenTelemetry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sessions.&lt;/strong&gt; What breaks when &lt;code dir=&quot;auto&quot;&gt;Mcp-Session-Id&lt;/code&gt; disappears? Sticky-session load balancer rules, every tool that assumes implicit per-connection state — and interrupted SSE streams, which are no longer resumed via &lt;code dir=&quot;auto&quot;&gt;Last-Event-ID&lt;/code&gt; but simply re-requested. Redesign around explicit handles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth.&lt;/strong&gt; Dynamic Client Registration is deprecated in favor of Client ID Metadata Documents (CIMD); clients must validate a present &lt;code dir=&quot;auto&quot;&gt;iss&lt;/code&gt; parameter (RFC 9207).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDKs.&lt;/strong&gt; The Tier 1 SDKs (TypeScript, Python, Go, C#) already speak 2026-07-28; they must mark deprecated API surfaces and should warn at runtime when one is used. Treat those warnings as inventory data, not noise.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The practical problem: answering “which servers?” means observing capability usage across your whole estate. That is awkward server by server — and natural at a chokepoint. A gateway terminates every client connection, so it is the one place where deprecated-capability usage shows up as &lt;em&gt;data&lt;/em&gt;, per backend and per client, and where a bridge between spec versions can live during the window. We made the broader visibility argument in &lt;a href=&quot;https://www.toolmesh.io/en/blog/why-mcp-gateways-alone-dont-solve-the-real-problem/&quot;&gt;Why MCP Gateways Alone Don’t Solve the Real Problem&lt;/a&gt;: the gateway is where you &lt;em&gt;see&lt;/em&gt;; what you enforce there is a separate design decision.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-the-stateless-spec-assumes-a-gateway&quot;&gt;Why the stateless spec assumes a gateway&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To be clear up front: the spec does not require a gateway — a client can still talk to an MCP server directly. But read the &lt;a href=&quot;https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http&quot;&gt;Streamable HTTP transport spec&lt;/a&gt; with infrastructure eyes and a pattern appears: the protocol is visibly built so that a layer in the middle becomes maximally effective. The spec states outright that request fields are mirrored into HTTP headers so that “intermediaries (load balancers, gateways, observability tooling)” can act on requests without parsing the body. A stateless request looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt; /mcp &lt;/span&gt;&lt;span&gt;HTTP&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;1.1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Content-Type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;application/json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;MCP-Protocol-Version&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2026-07-28&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Mcp-Method&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tools/call&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Mcp-Name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_device&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;jsonrpc&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;2.0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;method&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;tools/call&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;params&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;get_device&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;arguments&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sw-core-01&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;_meta&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;io.modelcontextprotocol/protocolVersion&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;2026-07-28&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;io.modelcontextprotocol/clientCapabilities&quot;&lt;/span&gt;&lt;span&gt;: {},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;io.modelcontextprotocol/clientInfo&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;example-client&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;version&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;1.0.0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Three details show how seriously the spec takes that middle layer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Header integrity is enforced.&lt;/strong&gt; Servers must reject requests whose headers disagree with the body (HTTP 400, error &lt;code dir=&quot;auto&quot;&gt;HeaderMismatch&lt;/code&gt;) — because a load balancer routing on a header while the server executes a different body would be a security hole. You only harden a mechanism like that if you expect components in the middle to act on it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameter routing is in the protocol.&lt;/strong&gt; A tool schema can mark parameters for header mirroring (&lt;code dir=&quot;auto&quot;&gt;x-mcp-header&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;Mcp-Param-*&lt;/code&gt;), so tenant, region, or project routing can happen at the edge, without body inspection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared caches are first-class.&lt;/strong&gt; Tool listings now declare how long they may be cached (&lt;code dir=&quot;auto&quot;&gt;ttlMs&lt;/code&gt;) and by whom (&lt;code dir=&quot;auto&quot;&gt;cacheScope&lt;/code&gt;); the value &lt;code dir=&quot;auto&quot;&gt;&quot;public&quot;&lt;/code&gt; exists precisely to allow shared caches, and deterministic tool ordering is recommended so catalog caches stay stable. (Catalog &lt;em&gt;size&lt;/em&gt; is its own problem; we wrote about it in &lt;a href=&quot;https://www.toolmesh.io/en/blog/tool-discovery-without-a-vector-database/&quot;&gt;Tool Discovery for 4,800 MCP Tools&lt;/a&gt;.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Put the halves together. Remote MCP traffic now behaves much more like ordinary HTTP traffic — something your infrastructure can route, throttle, meter, and cache. Cross-call application state becomes explicit: a server mints handles and manages their lifecycle. And credentials, token exchange, policies, and audit remain operator responsibilities either way. In larger MCP estates those responsibilities bundle naturally in one place: an application-layer control plane between agents and servers.&lt;/p&gt;
&lt;p&gt;This is not just our reading. AWS shipped &lt;a href=&quot;https://aws.amazon.com/blogs/machine-learning/how-agentcore-gateway-supports-the-mcp-2026-07-28-spec&quot;&gt;AgentCore Gateway support for 2026-07-28&lt;/a&gt; on release day, including per-request version bridging so old and new clients coexist. An &lt;a href=&quot;https://arxiv.org/abs/2608.10760&quot;&gt;enterprise deployment paper on arXiv&lt;/a&gt; (August 11, 2026) describes the governance problem of dozens of independently authenticated MCP servers and resolves it with exactly this component: one gateway fronting every downstream server. And &lt;a href=&quot;https://www.infoq.com/news/2026/08/mcp-stateless-gateway&quot;&gt;InfoQ’s coverage&lt;/a&gt; (August 12, 2026) observes that the new headers hand gateways the same primitives they already apply to every other API.&lt;/p&gt;
&lt;p&gt;The skeptics see the same facts and invert them. InfoQ’s headline asks whether stateless MCP is “just an API again”; the &lt;a href=&quot;https://news.ycombinator.com/item?id=49088058&quot;&gt;Hacker News thread&lt;/a&gt; adds sharper versions: the revision is wire-incompatible in both directions, dropping implicit session state is a real regression for tools that leaned on it, and date-based spec versions make the churn hard to track. Fair objections. But notice what they concede: if the protocol layer is now ordinary HTTP, then everything still specific to MCP in your deployment — which agents may call which tools, with whose credentials, under which policies, leaving which audit trail — is decided by whatever you place in the middle. The spec did not make that layer unnecessary. It made it the place where the decisions live.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-a-gateway-does-not-solve&quot;&gt;What a gateway does not solve&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Honesty cuts both ways, so here is the list we would want to read ourselves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It does not rewrite your servers.&lt;/strong&gt; If server code calls &lt;code dir=&quot;auto&quot;&gt;sampling/createMessage&lt;/code&gt;, that code has to change. A gateway shows you where; it does not do the work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sampling needs a real replacement, not a shim.&lt;/strong&gt; Moving model access to a provider API relocates keys, budgets, and logging. Decide it deliberately rather than letting the deadline decide it for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stateful designs need redesign, not translation.&lt;/strong&gt; The Hacker News objection is legitimate: explicit handles cost schema surface and tokens compared to implicit session context. The spec chose operability; some tool designs pay for it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bridging buys time, not completion.&lt;/strong&gt; A gateway that speaks both revisions extends coexistence. The deprecation window still ends.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Header-based policy has a trust boundary.&lt;/strong&gt; The spec itself warns intermediaries to trust mirrored headers only when the declared protocol version enforces header–body validation, and to reject older traffic rather than act on unvalidated headers. And origin servers should keep validating regardless — a gateway must never become the only checkpoint.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;where-toolmesh-stands&quot;&gt;Where ToolMesh stands&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh&quot;&gt;ToolMesh&lt;/a&gt; is an Apache-2.0, self-hosted MCP gateway written in Go that sits between AI agents and backend systems and runs every tool call through one fail-closed pipeline: authenticate → authorize → inject credentials → gate output → execute → audit. The state the new spec pushes out of the protocol is the kind of state a control plane holds on purpose: backend credentials are injected server-side and never travel through the model, and every call lands in a structured audit log — the raw material for exactly the deprecated-capability inventory described above. Backends are declared in &lt;a href=&quot;https://dadl.ai&quot;&gt;DADL&lt;/a&gt;, YAML files in git, so the tool catalog is deterministic by construction — which is what the new caching rules (&lt;code dir=&quot;auto&quot;&gt;ttlMs&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cacheScope&lt;/code&gt;, stable ordering) are designed to reward. The pillars behind that pipeline are laid out in &lt;a href=&quot;https://www.toolmesh.io/en/blog/anatomy-of-an-enterprise-tool-library/&quot;&gt;Anatomy of an Enterprise Tool Library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Our own production instance speaks Streamable HTTP and STDIO today, and its migration to 2026-07-28 is in progress — transport, SDK, and a session audit of our own backends. We would rather report findings than predictions, so we will share those notes once the migration lands.&lt;/p&gt;
&lt;p&gt;If you are running MCP servers and starting the same audit, we would genuinely like to compare notes — &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh/discussions&quot;&gt;GitHub Discussions&lt;/a&gt; is open.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;reference-the-changes-at-a-glance&quot;&gt;Reference: the changes at a glance&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every breaking move with its SEP reference — the spec-level detail behind the summary above:&lt;/p&gt;






































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Change&lt;/th&gt;&lt;th&gt;What it means&lt;/th&gt;&lt;th&gt;Reference&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Protocol sessions removed — &lt;code dir=&quot;auto&quot;&gt;Mcp-Session-Id&lt;/code&gt; is gone&lt;/td&gt;&lt;td&gt;Any request can hit any instance; cross-call state becomes explicit handles&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567&quot;&gt;SEP-2567&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;initialize&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;initialized&lt;/code&gt; handshake removed&lt;/td&gt;&lt;td&gt;Version, identity, and capabilities travel per request in &lt;code dir=&quot;auto&quot;&gt;_meta&lt;/code&gt;; optional &lt;code dir=&quot;auto&quot;&gt;server/discover&lt;/code&gt; RPC for up-front discovery&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575&quot;&gt;SEP-2575&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;subscriptions/listen&lt;/code&gt; replaces the HTTP GET endpoint and &lt;code dir=&quot;auto&quot;&gt;resources/subscribe&lt;/code&gt;&lt;/td&gt;&lt;td&gt;One long-lived, opt-in response stream carries change notifications&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575&quot;&gt;SEP-2575&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ping&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;logging/setLevel&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;notifications/roots/list_changed&lt;/code&gt; removed&lt;/td&gt;&lt;td&gt;Log level is set per request via &lt;code dir=&quot;auto&quot;&gt;_meta&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;io.modelcontextprotocol/logLevel&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575&quot;&gt;SEP-2575&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Mcp-Method&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;Mcp-Name&lt;/code&gt; headers required on Streamable HTTP&lt;/td&gt;&lt;td&gt;Intermediaries route, authorize, and meter without parsing JSON bodies&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243&quot;&gt;SEP-2243&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ttlMs&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;cacheScope&lt;/code&gt; required on list results and &lt;code dir=&quot;auto&quot;&gt;resources/read&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Tool catalogs become cacheable; &lt;code dir=&quot;auto&quot;&gt;&quot;public&quot;&lt;/code&gt; explicitly allows shared caches&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549&quot;&gt;SEP-2549&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multi Round-Trip Requests (MRTR) replace server-initiated requests&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;sampling/createMessage&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;elicitation/create&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;roots/list&lt;/code&gt; become &lt;code dir=&quot;auto&quot;&gt;input_required&lt;/code&gt; round-trips&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322&quot;&gt;SEP-2322&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;All results carry a required &lt;code dir=&quot;auto&quot;&gt;resultType&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;&quot;complete&quot;&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;&quot;input_required&quot;&lt;/code&gt;; results from earlier-protocol servers without it are treated as complete&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322&quot;&gt;SEP-2322&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Tasks move out of the core&lt;/td&gt;&lt;td&gt;Official extension &lt;code dir=&quot;auto&quot;&gt;io.modelcontextprotocol/tasks&lt;/code&gt;, poll-based&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663&quot;&gt;SEP-2663&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Roots, Sampling, Logging deprecated&lt;/td&gt;&lt;td&gt;Still work for at least twelve months; new implementations should not adopt them&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577&quot;&gt;SEP-2577&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Legacy HTTP+SSE transport reclassified as Deprecated&lt;/td&gt;&lt;td&gt;Migrate to Streamable HTTP&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596&quot;&gt;SEP-2596&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Dynamic Client Registration deprecated&lt;/td&gt;&lt;td&gt;Client ID Metadata Documents (CIMD) become the registration path; RFC 9207 &lt;code dir=&quot;auto&quot;&gt;iss&lt;/code&gt; validation required&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://modelcontextprotocol.io/specification/2026-07-28/changelog&quot;&gt;changelog&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Servers must additionally reject requests whose routing headers disagree with the body: HTTP 400 with JSON-RPC error &lt;code dir=&quot;auto&quot;&gt;-32020&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;HeaderMismatch&lt;/code&gt;.&lt;/p&gt;</content:encoded><category>MCP</category><category>Architecture</category></item><item><title>Editing Files Without an Editor</title><link>https://www.toolmesh.io/en/blog/editing-files-without-an-editor/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/editing-files-without-an-editor/</guid><description>An LLM has no cursor and no reliable sense of line numbers — so how does it change a file? Line numbers drift; exact quotes fail closed. A practical design for LLM file-editing APIs: content-as-address, the ambiguity round-trip, and optimistic concurrency.

</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In part one of this series — &lt;a href=&quot;https://www.dunkel.cloud/blog/die-haende-der-ki/&quot;&gt;Die Hände der KI&lt;/a&gt;, in German — we argued that a language model is a voice without hands: everything that looks like &lt;em&gt;doing&lt;/em&gt; is a tool layer wrapped around it, and that layer is where control lives. This post goes one level deeper, into the mechanics of a single finger. Because even with hands, a model faces a problem that rarely gets talked about:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It has no editor.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No cursor. No selection. No Ctrl+F highlighting the third match. A human edits text by &lt;em&gt;pointing at it&lt;/em&gt;. A model cannot point. It can only describe.&lt;/p&gt;
&lt;p&gt;So how does an AI actually change a file?&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;counting-is-not-describing&quot;&gt;Counting is not describing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The naive answer is to describe positions: “replace lines 45–52 with this.” It is how humans talk about code, it is how &lt;code dir=&quot;auto&quot;&gt;sed&lt;/code&gt; works, and it fails for models in two compounding ways.&lt;/p&gt;
&lt;p&gt;First, language models are genuinely bad at counting. They do not perceive a file as numbered lines — they see a token stream. Ask a model for the 45th line of a long file and you get an approximation, delivered with confidence.&lt;/p&gt;
&lt;p&gt;Second, and more fundamental: &lt;strong&gt;positional addresses expire the moment you use them.&lt;/strong&gt; Every successful edit shifts every line number below it. In a multi-step editing session the model’s map of the file drifts further from reality with each step. And a bare line-number edit fails &lt;em&gt;silently&lt;/em&gt; — line 45 always exists, it is just no longer the line you meant. (Classic &lt;code dir=&quot;auto&quot;&gt;patch&lt;/code&gt; survives drift precisely because its hunks carry quoted context lines: the escape hatch already points at content.)&lt;/p&gt;
&lt;p&gt;The research record is blunt about this. A 2026 study of edit formats (&lt;a href=&quot;https://arxiv.org/abs/2604.27296&quot;&gt;To Diff or Not to Diff?&lt;/a&gt;) identifies fragile offsets and fragmented hunks as a core reason classic unified diffs are unnatural for models to generate — the &lt;code dir=&quot;auto&quot;&gt;@@ -45,7 +45,9 @@&lt;/code&gt; hunk header is a small counting exercise, and models reliably get it wrong. Another paper (&lt;a href=&quot;https://arxiv.org/abs/2604.18170&quot;&gt;Copy-as-Decode&lt;/a&gt;) ran the controlled version of the experiment, perturbing otherwise-correct positional edit references by single steps: pooled exact match dropped from 100% to 15.48%.&lt;/p&gt;
&lt;p&gt;One line of drift, 85 points gone. Positions are the wrong address space.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-text-is-the-address&quot;&gt;The text is the address&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;What models do far better is &lt;em&gt;reproduce text they have just read&lt;/em&gt; — quoting is much closer to their native operation than counting is. So the robust design, arrived at independently by most of the tools that expose file editing directly to an LLM, is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The text to be replaced is the address.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The model sends an exact quote of what should change (&lt;code dir=&quot;auto&quot;&gt;old_str&lt;/code&gt;) and its replacement (&lt;code dir=&quot;auto&quot;&gt;new_str&lt;/code&gt;):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;old_str&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;timeout: 30&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;retries: 3&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;new_str&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;timeout: 60&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;retries: 5&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;No positions anywhere. The API searches for the quote and requires it to be unique.&lt;/p&gt;
&lt;p&gt;The quiet superpower of this scheme is that it is &lt;strong&gt;self-validating&lt;/strong&gt; — in the one dimension an API can actually check: the quoted text either exists uniquely, or nothing happens and the call fails loudly. That failure carries meaning: &lt;em&gt;the model’s picture of the file is stale.&lt;/em&gt; Wrong line numbers produce the opposite — an edit that “succeeds” somewhere unintended. That is silent corruption, the worst failure an editing API can have, because in agent workflows nobody is looking at the file afterwards.&lt;/p&gt;
&lt;p&gt;Loud failure is a feature. It is the API telling the model: re-read, then try again.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-the-quote-is-not-unique&quot;&gt;When the quote is not unique&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The obvious objection: what if &lt;code dir=&quot;auto&quot;&gt;timeout: 30&lt;/code&gt; appears four times?&lt;/p&gt;
&lt;p&gt;The tempting fix is to teach the API about document structure — “replace in section X”, “inside function Y”. That road leads to a format-aware API for Markdown, another for YAML, another for Go. The better fix is to apply the same principle recursively: &lt;strong&gt;scope by quoting, too.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Two generic mechanisms cover it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Anchors.&lt;/strong&gt; Optional &lt;code dir=&quot;auto&quot;&gt;after&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;before&lt;/code&gt; parameters take quotes that bracket a window; &lt;code dir=&quot;auto&quot;&gt;old_str&lt;/code&gt; only needs to be unique &lt;em&gt;inside the window&lt;/em&gt;. Headings, function signatures, config keys — the API does not need to know they are “structure”. They are just strings that happen to be naturally unique, and the model is well equipped to pick them. The API stays content-agnostic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The occurrence round-trip.&lt;/strong&gt; If the model does not scope and the quote is ambiguous, the API rejects the call — with numbered findings:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;error&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ambiguous&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;matches&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;&quot;occurrence&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;connect:&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;  timeout: 30&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;&quot;occurrence&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;read:&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;  timeout: 30&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;&quot;occurrence&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;write:&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;  timeout: 30&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;retry&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;resend with occurrence: N, or narrow with after/before&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The model retries with &lt;code dir=&quot;auto&quot;&gt;occurrence: 2&lt;/code&gt;. Note what did &lt;em&gt;not&lt;/em&gt; happen: the model never counted. The index was assigned by the API, is fresh by construction, and is verifiable against the context shown. Counting has been moved from the party that is bad at it to the party that is trivially good at it.&lt;/p&gt;
&lt;p&gt;Anchors are not free — the quoted context costs tokens and eats part of what scoping saves. For a small edit in a large file it is still far cheaper than any alternative.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;select-and-swap&quot;&gt;Select and swap&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Some edits replace a whole block. Reproducing forty lines as &lt;code dir=&quot;auto&quot;&gt;old_str&lt;/code&gt; is token waste and a fragility source — one transcription slip in line 23 and nothing matches. The editor gesture for this is click, then shift-click. The API equivalent is &lt;code dir=&quot;auto&quot;&gt;replace_range&lt;/code&gt;: quote the start, quote the end, replace everything between, inclusive:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;from_str&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;## Deployment&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;to_str&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;systemctl restart toolmesh&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;new_str&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;## Deployment&lt;/span&gt;&lt;span&gt;\n\n&lt;/span&gt;&lt;span&gt;Ship it with the new runbook: ...&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The model describes the &lt;em&gt;boundaries&lt;/em&gt; instead of reproducing the body. The boundary quotes follow the same rules as any quote — unique in scope, start before end, validated against the same revision. And because the body is unseen, a well-behaved API offers a &lt;code dir=&quot;auto&quot;&gt;dry_run&lt;/code&gt; that shows what &lt;em&gt;would&lt;/em&gt; be replaced, and echoes the actually replaced text back after a committed write.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-file-changed-under-you&quot;&gt;The file changed under you&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Agents do not edit alone. Another agent, a human, a cron job — anything may have touched the file since it was read. An edit based on a stale read is the lost-update problem, and content addressing alone does not fully solve it: the quote might still match even though the surrounding document has moved on.&lt;/p&gt;
&lt;p&gt;The fix is thirty years old: optimistic concurrency. Every read returns a revision. Every write carries it back as &lt;code dir=&quot;auto&quot;&gt;base_rev&lt;/code&gt;. If the file has changed in between, the write is rejected and the model re-reads and re-quotes. Web developers know this as ETag and &lt;code dir=&quot;auto&quot;&gt;If-Match&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Almost nothing in this protocol is novel, and that is the point. The only genuinely new decision is &lt;em&gt;what gets addressed&lt;/em&gt; — content instead of positions — because the caller is a system that quotes flawlessly and counts badly.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;an-escalation-ladder-not-a-single-tool&quot;&gt;An escalation ladder, not a single tool&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In practice the protocol is a ladder, and the tool description should say so:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Small correction → bare &lt;code dir=&quot;auto&quot;&gt;str_replace&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ambiguous match → add &lt;code dir=&quot;auto&quot;&gt;after&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;before&lt;/code&gt;, or take the &lt;code dir=&quot;auto&quot;&gt;occurrence&lt;/code&gt; the error just handed you.&lt;/li&gt;
&lt;li&gt;Substantial block rework → &lt;code dir=&quot;auto&quot;&gt;replace_range&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;str_replace&lt;/code&gt; with the whole paragraph as the quote.&lt;/li&gt;
&lt;li&gt;Document under one or two thousand tokens → a full rewrite is legitimate and the most robust move of all. It simply does not scale.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The anti-pattern sits at the top rung: the unscoped full rewrite of a &lt;em&gt;large&lt;/em&gt; file. It invites truncation laziness (”… rest of file unchanged …”) and drive-by edits in passages nobody meant to touch. Scoped tools exist precisely so the model never has to hold the whole file in its output.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-tools-that-ship-converged-on-this&quot;&gt;The tools that ship converged on this&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Look at the editing tools that get real production use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Anthropic’s public &lt;code dir=&quot;auto&quot;&gt;text_editor&lt;/code&gt; tool&lt;/strong&gt; is built on &lt;code dir=&quot;auto&quot;&gt;str_replace&lt;/code&gt; with enforced uniqueness.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://aider.chat/docs/leaderboards/&quot;&gt;Aider&lt;/a&gt;&lt;/strong&gt; standardized on search/replace blocks after extensive benchmarking; its leaderboard scores models on emitting the edit format correctly, not just on solving the task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAI’s &lt;a href=&quot;https://github.com/openai/codex&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;apply_patch&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; (the V4A patch format used by Codex) drops line numbers and addresses each hunk by quoted context lines.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The benchmark record agrees. JetBrains Research’s &lt;a href=&quot;https://arxiv.org/abs/2510.12487&quot;&gt;Diff-XYZ&lt;/a&gt; compared edit representations head-to-head; search-replace “performs best for larger models across most tasks” — GPT-4.1 applies edits with it at 0.96 exact match.&lt;/p&gt;
&lt;p&gt;There is a second school of thought — Cursor’s fast-apply and Morph-style merge models — that accepts lazy, human-style edits from the big model and trains a small specialized model to apply them. Different mechanics, same underlying distrust of raw positional output against a real file.&lt;/p&gt;
&lt;p&gt;When Anthropic, OpenAI, Aider and the benchmark record all land on the same core move — address edits by quoted content — that is not fashion. That is the shape of the constraint.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-living-proof-the-wiki-this-post-was-planned-in&quot;&gt;A living proof: the wiki this post was planned in&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We did not arrive at any of this in theory. Our team wiki — where the outline for this very article lived before it became a post — is a small internal tool called &lt;strong&gt;Tabula&lt;/strong&gt;: an LLM-native Markdown wiki over a plain git working tree, built to be edited by agents as much as by people.&lt;/p&gt;
&lt;p&gt;Its entire write path is the protocol above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;read_page&lt;/code&gt; returns &lt;code dir=&quot;auto&quot;&gt;{content, rev}&lt;/code&gt; — the revision is the future &lt;code dir=&quot;auto&quot;&gt;base_rev&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;edit_page&lt;/code&gt; is &lt;code dir=&quot;auto&quot;&gt;str_replace&lt;/code&gt;: quote, replace, done. An empty &lt;code dir=&quot;auto&quot;&gt;old_str&lt;/code&gt; is rejected; inserts reproduce a surrounding anchor instead.&lt;/li&gt;
&lt;li&gt;An ambiguous quote returns &lt;code dir=&quot;auto&quot;&gt;422 ambiguous&lt;/code&gt; with numbered findings and retry options — &lt;code dir=&quot;auto&quot;&gt;occurrence: N&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;after&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;before&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;replace_range&lt;/code&gt; covers block rework; &lt;code dir=&quot;auto&quot;&gt;batch_edit&lt;/code&gt; applies several edits atomically — all validate or none apply.&lt;/li&gt;
&lt;li&gt;Every write requires a change summary and lands as &lt;strong&gt;one git commit&lt;/strong&gt;. &lt;code dir=&quot;auto&quot;&gt;git log&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; the audit trail.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Agents edit this wiki daily. Ambiguous matches happen — self-similar prose like config tables and status lists triggers them far more often than code does — and they resolve in a single round-trip. What we have not seen once is a silently corrupted page. That trade, loud failure for cheap recovery, is the entire design.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;hands-with-rules&quot;&gt;Hands, with rules&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Part one ended with a claim: intelligence will be rented — your hands should be your own. This post is one of those hands up close: not a window with a blinking cursor, but a narrow protocol of quotes, revisions and honest errors.&lt;/p&gt;
&lt;p&gt;The narrowness is what makes the hand governable: every edit names exactly the text it touches, declares the revision it was based on, and lands as a commit someone can diff, revert and attribute. In &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh&quot;&gt;ToolMesh&lt;/a&gt;, our self-hosted gateway, an editing backend like this is just another tool behind the usual permission and audit surface — a read-only agent never sees the write tools at all.&lt;/p&gt;
&lt;p&gt;An AI’s editor was never going to look like ours. For an unattended caller, it is better: it cannot touch a line without naming it first.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you have built — or fought — an editing interface for agents, we would genuinely like to compare notes in &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh/discussions&quot;&gt;GitHub Discussions&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>AI</category><category>MCP</category><category>Architecture</category></item><item><title>Reconciling a UniFi site against NetBox in one prompt</title><link>https://www.toolmesh.io/en/blog/unifi-network-to-netbox/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/unifi-network-to-netbox/</guid><description>NetBox is only a source of truth if it still matches the network. Here is how one natural-language instruction reconciles a live UniFi controller against NetBox, writes only the differences, and proves the boring but important property: when nothing has drifted, it changes nothing.

</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A source of truth is only true until someone touches the network. Swap an access point, let DHCP hand out a new management address, add a unit in a back room, and the controller is immediately right while NetBox is quietly wrong. Nobody catches that by hand-diffing ten devices every week, so the documentation drifts and the trust in it drifts with it.&lt;/p&gt;
&lt;p&gt;The usual answer is a sync script. We wanted to give one instruction instead:&lt;/p&gt;
&lt;aside aria-label=&quot;Prompt&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Prompt&lt;/p&gt;&lt;div&gt;&lt;p&gt;Reconcile the HOME site on the UniFi controller against NetBox. Add anything new, fix changed models or moved management IPs, and leave correct records alone.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;No pipeline. No field mapping spelled out. When the network changes next month, the same sentence runs again.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-happens-underneath&quot;&gt;What happens underneath&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The agent talks to two backends through one gateway: it reads the live UniFi controller and reads and writes NetBox, and every call goes through the gateway’s audited, policy-gated surface rather than a shell or a stored admin token. In Code Mode it is a short script the model writes itself, and its shape is a comparison, not a bulk import:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;site&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;23by5njv&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;                           &lt;/span&gt;&lt;span&gt;// desc: &quot;HOME&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;live&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;unifi_list_devices&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;site&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;live&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;MODEL_MAP&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; ?? &lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt;;   &lt;/span&gt;&lt;span&gt;// U7PG2 -&gt; &quot;UniFi AC Pro Gen2&quot;, etc.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;nb&lt;/span&gt;&lt;span&gt;]  = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;netbox_list_devices&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;nb&lt;/span&gt;&lt;span&gt;) {                                        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// new device -&gt; create&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;netbox_create_device&lt;/span&gt;&lt;span&gt;({ name: &lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, device_type: &lt;/span&gt;&lt;span&gt;typeId&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt;), role: &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP&lt;/span&gt;&lt;span&gt;, site: &lt;/span&gt;&lt;span&gt;LII&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;created&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;mgmtIp&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;nb&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;) {               &lt;/span&gt;&lt;span&gt;// moved address -&gt; update one field&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;netbox_update_device&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;nb&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;, { primary_ip4: &lt;/span&gt;&lt;span&gt;ipId&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;) });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;updated mgmt IP&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;mgmtIp&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;nb&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ip&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ap&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);                            &lt;/span&gt;&lt;span&gt;// already matches -&gt; write nothing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Two things in there are worth pulling out, because they are where reconciliation is harder than import.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The model codes are not product names.&lt;/strong&gt; To decide whether a UniFi device already matches its NetBox record, the comparison has to normalize what the controller reports. UniFi returns each device as an internal code: &lt;code dir=&quot;auto&quot;&gt;U7PG2&lt;/code&gt; is the UAP-AC-Pro (the &lt;code dir=&quot;auto&quot;&gt;7&lt;/code&gt; has nothing to do with Wi-Fi 7), &lt;code dir=&quot;auto&quot;&gt;UAL6&lt;/code&gt; is a U6 Lite, &lt;code dir=&quot;auto&quot;&gt;U7NHD&lt;/code&gt; a nanoHD, &lt;code dir=&quot;auto&quot;&gt;U6EXT&lt;/code&gt; a U6 Extender, &lt;code dir=&quot;auto&quot;&gt;U7PRO&lt;/code&gt; an actual U7 Pro, and &lt;code dir=&quot;auto&quot;&gt;US8P60&lt;/code&gt; the US-8-60W switch. That translation table is the difference between “this AP is already correct” and a duplicate device created next to the real one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Leave correct records alone” is the load-bearing clause.&lt;/strong&gt; Every branch above is a get-or-create-or-update. A matching device produces a write of nothing. That is what makes the sentence safe to run on a schedule against a NetBox that already has data, instead of a one-time import you never dare repeat.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-this-run-actually-did&quot;&gt;What this run actually did&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We ran it against the live controller. Here is the real result, not a constructed one:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;reconcile HOME -&gt; NetBox (site LII)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP-Kueche               U7NHD    192.168.100.43    ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP-Praxis               U7NHD    192.168.100.46    ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP-Labor                U7PG2    192.168.100.41    ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP-Buero                U7NHD    192.168.100.44    ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP-Garage               U7PG2    192.168.100.42    ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP-U7-Pro Heizungsraum  U7PRO    192.168.100.154   ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP-U6-Extender          U6EXT    192.168.100.199   ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP-Mobil                UAL6     192.168.100.47    ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AP-Wohnzimmer           U7PG2    192.168.100.45    ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;UbSwi-01                US8P60   192.168.100.183   ok&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-- 10 checked, 10 matched, 0 written.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Nine access points across five models and one PoE switch, every device already present, every management IP still matching, the IoT VLAN and the management prefix already in place. Net change to NetBox: zero.&lt;/p&gt;
&lt;p&gt;That is the anticlimax that is actually the point. An agent that writes nothing when nothing has drifted is precisely the property that lets you point it at a CMDB you care about and run it again and again. You do not have to trust it not to clobber your data. You can watch it do nothing.&lt;/p&gt;
&lt;p&gt;Drift is the case that earns its keep. If AP-Garage had come up on &lt;code dir=&quot;auto&quot;&gt;.142&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;.42&lt;/code&gt;, the run would have printed one line, &lt;code dir=&quot;auto&quot;&gt;updated mgmt IP AP-Garage 192.168.100.42 -&gt; 192.168.100.142&lt;/code&gt;, written that single record, and left the other nine reading &lt;code dir=&quot;auto&quot;&gt;ok&lt;/code&gt;. The audit log shows exactly which field changed, on which device, run by which account, with no diff to read through.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-definitions-need-the-same-scrutiny&quot;&gt;The definitions need the same scrutiny&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Reconciling the devices keeps the inventory honest. The device-type definitions behind them do not get checked by a name-and-IP pass, and they drift in their own way. This run was a good moment to catch one: the U7 Pro in the boiler room was linked, correctly, to a device type whose slug matched, but that type was still labelled “AC Pro, 802.11ac”. The device matched, so the reconciliation would not flag it; the label was simply wrong, since the model code and the unit’s Wi-Fi 7 firmware say otherwise. We corrected it in the same session. A source of truth is only as good as the definitions it points at, and those deserve a deliberate look rather than an assumption.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;i-have-done-this-with-nornir-for-years&quot;&gt;”I have done this with Nornir for years”&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You have, and it works. The difference is that there is no script here to maintain. The mapping table lives in the model’s context, the instruction is plain language, and “also reconcile the client list” or “skip the guest SSID” is a change to the sentence, not to a codebase and its tests. When Ubiquiti renames a model code in a controller update, you fix one line of a prompt.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;try-it-against-your-own-controller&quot;&gt;Try it against your own controller&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The UniFi and NetBox connectors are open: &lt;a href=&quot;https://dadl.ai&quot;&gt;DADL&lt;/a&gt;, one YAML file per API, behind an Apache-2.0 gateway. Clone it and point it at your own controller and your own NetBox. If you would rather have it run as a managed instance, talk to us at &lt;a href=&quot;mailto:vertrieb@dunkel.cloud&quot;&gt;vertrieb@dunkel.cloud&lt;/a&gt;. Questions and war stories are welcome in &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh/discussions&quot;&gt;GitHub Discussions&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>NetBox</category><category>UniFi</category><category>DADL</category><category>MCP</category><category>Infrastructure</category></item><item><title>Tool Discovery for 4,800 MCP Tools — Without a Vector Database</title><link>https://www.toolmesh.io/en/blog/tool-discovery-without-a-vector-database/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/tool-discovery-without-a-vector-database/</guid><description>One discovery call on our production gateway returned ~320 KB of TypeScript declarations — roughly 80,000 tokens gone before the agent had done anything. Here is how we fixed tool discovery at enterprise scale with tiered output, 200 lines of BM25, and no vector database.

</description><pubDate>Thu, 11 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On our production ToolMesh instance, asking &lt;code dir=&quot;auto&quot;&gt;discover_tools&lt;/code&gt; for everything NetBox-related returned about &lt;strong&gt;320 KB of TypeScript declarations&lt;/strong&gt;. That is roughly 80,000 tokens — for many models half the context window, spent before the agent has called a single tool. Asking for &lt;em&gt;all&lt;/em&gt; tools would have produced output on the order of 2 MB.&lt;/p&gt;
&lt;p&gt;Nobody designed it to do that. It grew into it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-a-discovery-call-gets-to-320-kb&quot;&gt;How a discovery call gets to 320 KB&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh is a self-hosted MCP gateway that speaks &lt;a href=&quot;https://www.toolmesh.io/en/architecture/&quot;&gt;Code Mode&lt;/a&gt;: backends are exposed as flat JavaScript functions (&lt;code dir=&quot;auto&quot;&gt;toolmesh.netbox_list_devices(...)&lt;/code&gt;), and a &lt;code dir=&quot;auto&quot;&gt;discover_tools&lt;/code&gt; meta-tool returns TypeScript signatures so the model can write correct calls. With a handful of backends, returning every matching signature is exactly right.&lt;/p&gt;
&lt;p&gt;Our instance is no longer a handful. It runs &lt;strong&gt;49 backends with 4,767 tools&lt;/strong&gt;: NetBox alone contributes 608, two MikroTik switches 297 each, two OPNsense firewalls 292 each, GitHub 203, Cloudflare 200. Each full signature averages ~530 bytes — NetBox list endpoints carry a dozen or more filter parameters.&lt;/p&gt;
&lt;p&gt;Three effects compound at this scale:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Full declarations are the only output mode.&lt;/strong&gt; Every match renders with its complete parameter list.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The search regex matches names &lt;em&gt;and&lt;/em&gt; descriptions.&lt;/strong&gt; A pattern like &lt;code dir=&quot;auto&quot;&gt;device&lt;/code&gt; fans out across NetBox, UniFi, MikroTik, and Shelly at once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-instance backends share prefixes.&lt;/strong&gt; Searching &lt;code dir=&quot;auto&quot;&gt;mikrotik&lt;/code&gt; returns both switches — 594 declarations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So a perfectly reasonable agent query — “show me the NetBox tools” — detonates the context window.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-fix-everyone-expects&quot;&gt;The fix everyone expects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The reflexive 2026 answer is: embed the tool descriptions, put them in a vector database, do semantic retrieval. We run that pattern elsewhere and it earns its keep there.&lt;/p&gt;
&lt;p&gt;For this problem we deliberately said no, for three reasons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The corpus is tiny and static.&lt;/strong&gt; ~4,800 short, well-named, well-described documents that only change on a config reload. That is an in-memory index rebuilt in milliseconds — not a persistence problem. Adding PostgreSQL/pgvector to a single-binary, self-hosted Go gateway to search five thousand strings is infrastructure out of all proportion. Even the lean path — an embedded ONNX model — drags in a native runtime library, which is real packaging pain for software other people deploy themselves.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The vocabulary is already aligned.&lt;/strong&gt; Tool names and descriptions are written for LLM consumption. Lexical ranking has excellent material to work with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most importantly: the agent is the semantic layer.&lt;/strong&gt; An LLM that sees a compact, ranked result list with an honest summary line reformulates its own query in one turn. It does not need a perfect search. It needs a &lt;strong&gt;cheap iteration loop&lt;/strong&gt; and feedback about what it is looking at.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-we-shipped-instead&quot;&gt;What we shipped instead&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Three changes, all in &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh/pull/80&quot;&gt;ToolMesh v0.3.1+&lt;/a&gt;, zero new dependencies.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-output-that-degrades-before-it-detonates&quot;&gt;1. Output that degrades before it detonates&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;discover_tools&lt;/code&gt; now scales its detail level to the number of matches: up to 25 matches return &lt;strong&gt;full TypeScript signatures&lt;/strong&gt;, up to 250 return &lt;strong&gt;one-line summaries&lt;/strong&gt;, up to 2,000 return &lt;strong&gt;names only&lt;/strong&gt;, and anything beyond that returns &lt;strong&gt;per-backend counts&lt;/strong&gt;. A &lt;code dir=&quot;auto&quot;&gt;detail&lt;/code&gt; parameter overrides the automatic choice, and a hard 50 KB cap backstops every tier.&lt;/p&gt;
&lt;p&gt;The former 2 MB worst case — no filter at all — now answers in about 1.5 KB:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// ToolMesh tools — per-backend overview&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rest:netbox — 608 tools&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rest:mikrotik-sw-lii-labor-perlman — 297 tools&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rest:mikrotik-sw-mikrotik-10g — 297 tools&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;rest:opnsense_backup — 292 tools&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// ── 4767 of 4767 tools matched — detail: overview (auto).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Refine: narrower pattern, query:&quot;&amp;#x3C;free text&gt;&quot; for ranked results,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;//         detail:&quot;full&quot;|&quot;summary&quot;|&quot;names&quot;|&quot;overview&quot;, limit:N.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That footer is the most important line in the feature. &lt;strong&gt;Every&lt;/strong&gt; response states how much of the catalog matched, how much is shown, and how to refine. The agent never mistakes a truncated view for the whole world — and it self-corrects in the next call instead of guessing.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-ranked-free-text-search--bm25-in-200-lines-of-go&quot;&gt;2. Ranked free-text search — BM25 in ~200 lines of Go&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Regex patterns are great for exact lookups (&lt;code dir=&quot;auto&quot;&gt;^netbox_list&lt;/code&gt;), bad for exploration. The new &lt;code dir=&quot;auto&quot;&gt;query&lt;/code&gt; parameter does ranked free-text search:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;discover_tools({ query: &quot;create dns record for a zone&quot; })&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;returns the top 25 by relevance — Cloudflare, INWX, and Linode DNS tools, in that mixed order, with no NetBox noise. Under the hood is a plain BM25 index over tool names (boosted), descriptions, and parameter names — about 200 lines of dependency-free Go, rebuilt per call in microseconds. Parameter names being indexed means a query like &lt;code dir=&quot;auto&quot;&gt;rack_id&lt;/code&gt; finds &lt;code dir=&quot;auto&quot;&gt;netbox_list_devices&lt;/code&gt; even though “rack” appears nowhere in the tool name.&lt;/p&gt;
&lt;p&gt;BM25 is fifty-year-old information retrieval. That is not a weakness; at this corpus size it is the entire point.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-discovery-inside-the-sandbox&quot;&gt;3. Discovery inside the sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The biggest structural win: agents no longer need a separate discovery round-trip at all. Inside &lt;code dir=&quot;auto&quot;&gt;execute_code&lt;/code&gt;, two local helpers are available:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// find → inspect → call, in ONE round trip&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;hits&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;discover&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;move task to kanban bucket&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;describe&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;hits&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;hits&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;]({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;project_id: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, view_id: &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;, task_id: &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;, bucket_id: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;toolmesh.discover()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;toolmesh.describe()&lt;/code&gt; run against the in-memory index — no backend call, no context cost beyond what the code explicitly returns, exempt from the per-execution call budget, and filtered by the caller’s authorization like every other surface. Discovery becomes something the &lt;em&gt;code&lt;/em&gt; does, not something the &lt;em&gt;context&lt;/em&gt; pays for.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-live-testing-caught-within-twenty-minutes&quot;&gt;What live testing caught within twenty minutes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We deployed the branch to production and drove it from a real agent session. Two bugs surfaced almost immediately — both invisible to unit tests:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A stale client schema turned &lt;code dir=&quot;auto&quot;&gt;limit&lt;/code&gt; into a string.&lt;/strong&gt; The MCP client had cached the old &lt;code dir=&quot;auto&quot;&gt;discover_tools&lt;/code&gt; schema, so it serialized the unknown parameter &lt;code dir=&quot;auto&quot;&gt;limit: 5&lt;/code&gt; as the string &lt;code dir=&quot;auto&quot;&gt;&quot;5&quot;&lt;/code&gt; — which the server silently ignored. Clients you do not control will hold stale schemas; agent-facing parameters now accept numeric strings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summaries truncated at “e.g.”&lt;/strong&gt; The one-line summarizer cut at the first period-plus-space, producing &lt;code dir=&quot;auto&quot;&gt;Set device key properties (e.g.&lt;/code&gt; — a period now only ends a sentence when followed by an uppercase letter.&lt;/p&gt;
&lt;p&gt;Neither is glamorous. Both are the difference between a feature that demos well and one that survives contact with real clients.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;prior-art-and-what-changes-at-scale&quot;&gt;Prior art, and what changes at scale&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Progressive disclosure for tools is not our invention. Anthropic’s &lt;a href=&quot;https://www.anthropic.com/engineering/code-execution-with-mcp&quot;&gt;code execution with MCP&lt;/a&gt; post describes loading tool definitions on demand; Claude Code defers tool schemas until needed; Cloudflare’s Code Mode made the write-code-against-tools pattern mainstream. The pattern is settling into consensus.&lt;/p&gt;
&lt;p&gt;What production scale adds is the parts that rarely show up in demos: ranking (because 764 candidates match “dns” &lt;em&gt;somewhere&lt;/em&gt;), an always-on feedback line (because agents refine well but only when told what they saw), hard output caps (because some client will always ask for everything), and in-sandbox discovery (because the cheapest context is the one never spent).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The same index has two more consumers coming: a &lt;strong&gt;capability index for nested Code Mode backends&lt;/strong&gt; — MCP servers that themselves only expose &lt;code dir=&quot;auto&quot;&gt;search&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;execute&lt;/code&gt;, whose capabilities ToolMesh will probe and index at registration — and &lt;strong&gt;synthetic tool descriptions&lt;/strong&gt; generated from it at the MCP layer. And if real-world vocabulary misses ever justify it, an optional embedding channel can slot in behind the same &lt;code dir=&quot;auto&quot;&gt;query&lt;/code&gt; API — measured first, added second.&lt;/p&gt;
&lt;p&gt;The implementation is open source (&lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh/pull/80&quot;&gt;PR #80&lt;/a&gt;, Apache 2.0). The tool definitions it searches come from &lt;a href=&quot;https://dadl.ai&quot;&gt;DADL&lt;/a&gt; — one YAML file per API, 25+ ready-made in the registry.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you run an MCP setup that has outgrown its tool list: we would genuinely like to hear what discovery looks like at your scale — &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh/discussions&quot;&gt;GitHub Discussions&lt;/a&gt; or &lt;a href=&quot;mailto:presse@dunkel.cloud&quot;&gt;presse@dunkel.cloud&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content:encoded><category>MCP</category><category>Code Mode</category><category>Architecture</category></item><item><title>From Switch Config to NetBox in One Prompt</title><link>https://www.toolmesh.io/en/blog/switch-config-to-netbox/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/switch-config-to-netbox/</guid><description>Your NetBox says one thing, your switch says another. Here is how to reconcile them with a single natural-language instruction instead of a Python script you have to maintain forever.

</description><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h2 id=&quot;the-situation&quot;&gt;The situation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You run NetBox as your source of truth. It is good, until it isn’t.&lt;/p&gt;
&lt;p&gt;Last Tuesday someone swapped a switch in rack B. They added two VLANs, renamed a couple of interfaces, and moved an uplink. NetBox knows none of it. Now your IPAM is quietly wrong, your cable map lies, and the next person who trusts NetBox to plan a change is going to have a bad afternoon.&lt;/p&gt;
&lt;p&gt;The usual fix is a sync script. You write a few hundred lines that pull the live config, diff it against NetBox, and push the deltas. It works. Then the vendor ships a firmware update, an interface naming scheme changes, you add a second switch model, and now you maintain the script as carefully as the network it describes.&lt;/p&gt;
&lt;p&gt;There is a different shape to this.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-instruction&quot;&gt;The instruction&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is what the task looks like when the tooling carries the connectivity and you carry the intent:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Read the live configuration from the &lt;code dir=&quot;auto&quot;&gt;perlman&lt;/code&gt; switch. Compare its interfaces, IP addresses and VLANs against what NetBox has for that device. Tell me every difference, then update NetBox to match the switch — but stop and show me anything that looks like a removal before you delete it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is the whole thing. No script. Tomorrow the instruction is “…and also reconcile rack B’s MikroTik while you’re at it” and nothing in your codebase has to change, because there is no codebase. The instruction is the automation, and it is disposable by design.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-actually-happens-underneath&quot;&gt;What actually happens underneath&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is not magic, and the point of this series is that you can see exactly what runs. The agent has two tool surfaces in front of it, both served through a single ToolMesh endpoint:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the MikroTik switch, described by a &lt;a href=&quot;https://www.toolmesh.io/en/dadl/&quot;&gt;DADL&lt;/a&gt; file — about thirty lines of YAML turn its REST API into callable tools like &lt;code dir=&quot;auto&quot;&gt;export_config&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;list_interfaces&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;list_ip_addresses&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;NetBox, the same way — &lt;code dir=&quot;auto&quot;&gt;netbox_get_device&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;netbox_create_interface&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;netbox_create_ip_address&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;netbox_update_*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Code Mode the model does not fire one tool call per round-trip. It writes a short piece of JavaScript that runs server-side and only returns the result:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// pull live state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;live&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mikrotik_perlman_list_interfaces&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;liveIps&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mikrotik_perlman_list_ip_addresses&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// pull NetBox&apos;s view of the same device&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;device&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;netbox_get_device&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;perlman&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;nbIfaces&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;netbox_list_interfaces&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ device_id: &lt;/span&gt;&lt;span&gt;device&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// diff happens here, in code, not in the model&apos;s head&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;live&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;liveIps&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;device&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;nbIfaces&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;One execution, one result back to the model. A catalogue of eighty NetBox tools plus the switch’s tools would cost roughly 142,000 tokens to advertise on every reasoning step if you listed them all as JSON schemas. Code Mode keeps that closer to a thousand, flat, no matter how many backends you add. That is the difference between “this is neat in a demo” and “I can leave this running against my real network.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-parts-youd-worry-about-handled&quot;&gt;The parts you’d worry about, handled&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Nothing deletes silently.&lt;/strong&gt; The instruction said stop and show me removals, and that is enforced at the gate, not just left to the agent’s good intentions. Two core mechanisms do it. Authorization comes first: through OpenFGA, an agent can hold &lt;code dir=&quot;auto&quot;&gt;netbox_create_*&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;netbox_update_*&lt;/code&gt; but never &lt;code dir=&quot;auto&quot;&gt;netbox_delete_*&lt;/code&gt; — the delete tools are simply never unlocked for it. And the &lt;code dir=&quot;auto&quot;&gt;ExecuteTool()&lt;/code&gt; pipeline runs a Pre-Gate before any call reaches the backend, where a short hard rule — plain JavaScript — refuses anything matching &lt;code dir=&quot;auto&quot;&gt;netbox_delete_*&lt;/code&gt;. The pipeline is fail-closed: if a rule can’t decide, the call is denied, not waved through. Creates and updates flow; deletions are refused at the gate, so the agent shows you the removals it found and changes none of them. You write that once, and it holds for every prompt you ever run.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No credentials in the prompt.&lt;/strong&gt; The switch’s API token and the NetBox token never reach the model. They live in ToolMesh’s credential store and get injected at call time. The model orchestrates; it never sees a secret.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can prove what happened.&lt;/strong&gt; Every call lands in a structured audit log — which tool, which device, what changed, how long it took, success or denied. When someone asks in three weeks why interface &lt;code dir=&quot;auto&quot;&gt;sfp-sfpplus3&lt;/code&gt; got renamed, the answer is a query, not an archaeology project.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-this-is-and-what-it-isnt&quot;&gt;What this is, and what it isn’t&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If your reaction is “I’ve done config-to-NetBox with Nornir for ten years” — you have, and well. The claim here is not that this is more powerful than a purpose-built script. It’s that there is no script to maintain. The reconciliation logic isn’t pinned to one vendor, one naming scheme, or one NetBox version. The instruction adapts to the situation in plain language; the tools underneath are declared in YAML you can diff in Git. When the situation changes, you change a sentence, not a pipeline.&lt;/p&gt;
&lt;p&gt;That is the whole bet: network state changes constantly, so the layer that reconciles it should be the cheap, disposable part — not the part you babysit.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;want-to-try-it-on-your-own-network&quot;&gt;Want to try it on your own network?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The honest friction here is setup: standing up ToolMesh, writing the DADL for your switch, wiring NetBox. If that friction is the thing stopping you, skip it. ToolMesh is an offering of &lt;a href=&quot;https://www.dunkel.cloud/&quot;&gt;Dunkel Cloud GmbH&lt;/a&gt;, and we will set up a personal instance for you on its own VM — you keep full control and your own credentials, we just do the initial wiring so you can point it at your gear and see whether the reconciliation actually holds up against your reality.&lt;/p&gt;
&lt;p&gt;That offer is genuine, and it is the fastest way to find out if this fits how you work.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This is post 1 of a series on driving NetBox from real infrastructure: &lt;a href=&quot;https://dadl.ai/d/mikrotik&quot;&gt;MikroTik&lt;/a&gt;, OPNsense, &lt;a href=&quot;https://dadl.ai/d/xen-orchestra&quot;&gt;Xen Orchestra&lt;/a&gt;, &lt;a href=&quot;https://dadl.ai/d/hetzner-cloud&quot;&gt;Hetzner&lt;/a&gt;, vSphere, and UniFi. Each one is a real workflow from our own stack, not a contrived demo.&lt;/p&gt;
&lt;p&gt;ToolMesh is open source (Apache 2.0). DADL is an open spec (CC BY-SA 4.0) with a public registry at &lt;a href=&quot;https://dadl.ai/&quot;&gt;dadl.ai&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>NetBox</category><category>MikroTik</category><category>DADL</category><category>MCP</category><category>Infrastructure</category></item><item><title>Xen Orchestra&apos;s Full REST API — 252 Tools from One DADL</title><link>https://www.toolmesh.io/en/blog/xen-orchestra-full-rest-api-in-one-dadl/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/xen-orchestra-full-rest-api-in-one-dadl/</guid><description>Xen Orchestra stabilized its REST API in 6.4, and every release since has added a handful of endpoints — 6.6 brought writable backup repositories and PATCH for VDIs and VIFs. Vates ships a first-party MCP server — 7 tools, read-only by design. We took a different cut: the entire 252-endpoint surface, read and write, as one DADL. Both belong in the same registry. Here is why.

</description><pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Xen Orchestra has spent the last two releases turning its REST API into something you can build on. &lt;a href=&quot;https://xen-orchestra.com/blog/&quot;&gt;XO 6.4&lt;/a&gt; (April 2026) marked &lt;code dir=&quot;auto&quot;&gt;/rest/v0/&lt;/code&gt; as stable and added fine-grained RBAC v2; 6.5 added &lt;code dir=&quot;auto&quot;&gt;PATCH /vms/{id}&lt;/code&gt; for partial VM updates and a separate &lt;code dir=&quot;auto&quot;&gt;/vm-controllers&lt;/code&gt; collection; 6.6 (June 2026) continued the same trickle — scoped Storage and Network Administrator roles, writable backup repositories, and &lt;code dir=&quot;auto&quot;&gt;PATCH&lt;/code&gt; for VDIs and VIFs. In February, Vates also shipped a &lt;a href=&quot;https://xen-orchestra.com/blog/mcp-meets-xen-orchestra/&quot;&gt;first-party MCP server&lt;/a&gt; bundled with XO 6.2 — seven tools, deliberately read-only, so teams can enable it on production from day one.&lt;/p&gt;
&lt;p&gt;We took a different cut of the same API. The DADL for Xen Orchestra covers the &lt;strong&gt;entire 252-endpoint REST surface&lt;/strong&gt; — read and write — as one YAML file. This post is the build log, and an honest answer to the obvious question: why two MCP surfaces for the same product?&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-rest-api-now-covers&quot;&gt;What the REST API now covers&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The XO REST API is no longer a thin read layer. As of 6.6 it exposes the full object graph — VMs (including &lt;code dir=&quot;auto&quot;&gt;PATCH&lt;/code&gt; partial updates and snapshot revert), VM controllers, VM templates, VM/VDI snapshots, hosts, pools, storage (SR/VDI/VBD, with &lt;code dir=&quot;auto&quot;&gt;PATCH&lt;/code&gt; rename and disk-resize on VDIs since 6.6), networking (VIF/PIF/PBD, with &lt;code dir=&quot;auto&quot;&gt;PATCH&lt;/code&gt; on VIFs since 6.6), hardware passthrough (PCI/PGPU/SM) — plus every lifecycle action: power, snapshot, clone, migrate, export/import, hotplug. On top of the objects sit the operational surfaces: tasks, backups (jobs, logs, repositories — writable since 6.6 — restore, schedules), real-time change events over Server-Sent Events, RBAC v2 (users, groups, acl-roles, acl-privileges), and host/pool maintenance (rolling reboot, rolling update, emergency shutdown).&lt;/p&gt;
&lt;p&gt;That is a large API, and almost all of it mutates infrastructure. None of the write side is reachable through the first-party MCP server — by design, not by omission.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-numbers&quot;&gt;The numbers&lt;/h2&gt;&lt;/div&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;DADL (&lt;code dir=&quot;auto&quot;&gt;xen-orchestra.dadl&lt;/code&gt;)&lt;/th&gt;&lt;th&gt;&lt;code dir=&quot;auto&quot;&gt;@xen-orchestra/mcp&lt;/code&gt; (first-party)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Tools&lt;/td&gt;&lt;td&gt;&lt;strong&gt;252&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Scope&lt;/td&gt;&lt;td&gt;full read &lt;strong&gt;and&lt;/strong&gt; write&lt;/td&gt;&lt;td&gt;read-only by design&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Coverage&lt;/td&gt;&lt;td&gt;every REST object + lifecycle action (incl. &lt;code dir=&quot;auto&quot;&gt;PATCH&lt;/code&gt; on VMs, VDIs, VIFs), backups with writable repositories, RBAC v2, SSE events, SDN traffic rules, dashboards, auth tokens&lt;/td&gt;&lt;td&gt;infrastructure summary, list/inspect pools / hosts / VMs, pool dashboard, documentation search&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Create / start / migrate / delete?&lt;/td&gt;&lt;td&gt;yes&lt;/td&gt;&lt;td&gt;no — intentionally&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Distribution&lt;/td&gt;&lt;td&gt;one 252-tool YAML file&lt;/td&gt;&lt;td&gt;Node server bundled with XO 6.2+&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Tracking a new API release&lt;/td&gt;&lt;td&gt;~30 lines of YAML per endpoint&lt;/td&gt;&lt;td&gt;ships with the XO release&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Governance layer&lt;/td&gt;&lt;td&gt;credentials, authorization, audit (via ToolMesh)&lt;/td&gt;&lt;td&gt;none — local, in-process&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The local validation, run from the registry checkout:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; cd dadl-registry &amp;#x26;&amp;#x26; npm run validate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✅ xen-orchestra.dadl — 252 tools&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The full definition is live and browsable at &lt;a href=&quot;https://dadl.ai/d/xen-orchestra&quot;&gt;dadl.ai/d/xen-orchestra&lt;/a&gt; — the authoritative source, always current with the latest XO release. The DADL spec itself: &lt;a href=&quot;https://dadl.ai/spec/dadl-spec-v0.2.md&quot;&gt;dadl.ai/spec&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-worked-example-a-vm-change-made-undoable&quot;&gt;A worked example: a VM change, made undoable&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The point of the write side is that it actually changes infrastructure — safely, with a trail. Here is a three-call round trip: find a running VM, snapshot it so the change is reversible, then patch it.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 1. Find the VM by name (XO filter syntax, compact fields)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;vm&lt;/span&gt;&lt;span&gt;] = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;xen_orchestra_list_vms&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;filter: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;name_label:web-fra-01&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fields: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;uuid,name_label,tags&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 2. Snapshot first, so the change is undoable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;xen_orchestra_snapshot_vm&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id: &lt;/span&gt;&lt;span&gt;vm&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;uuid&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name_label: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;pre-change &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toISOString&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// 3. PATCH the live VM — tags is a hot field, applied while running (XO 6.5)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;xen_orchestra_update_vm&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;id: &lt;/span&gt;&lt;span&gt;vm&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;uuid&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tags: [&lt;/span&gt;&lt;span&gt;...vm&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;managed:toolmesh&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Three calls. No glue code. Steps 2 and 3 are exactly what a read-only MCP server cannot do — and the DADL transparently handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the unusual cookie-based auth (the token is injected server-side as &lt;code dir=&quot;auto&quot;&gt;Cookie: authenticationToken=…&lt;/code&gt; — the model never sees it);&lt;/li&gt;
&lt;li&gt;the async-by-default action model — most writes return a task reference; you add &lt;code dir=&quot;auto&quot;&gt;sync&lt;/code&gt; only for short operations and poll &lt;code dir=&quot;auto&quot;&gt;get_task&lt;/code&gt; for long ones (migrate, export) to avoid HTTP client timeouts;&lt;/li&gt;
&lt;li&gt;the cold-vs-hot field distinction on &lt;code dir=&quot;auto&quot;&gt;update_vm&lt;/code&gt; — &lt;code dir=&quot;auto&quot;&gt;memoryStaticMax&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cpusStaticMax&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;secureBoot&lt;/code&gt; need a halted VM, while &lt;code dir=&quot;auto&quot;&gt;tags&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;nameLabel&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;cpus&lt;/code&gt; apply live (the example uses &lt;code dir=&quot;auto&quot;&gt;tags&lt;/code&gt;, so it runs without downtime);&lt;/li&gt;
&lt;li&gt;retries on transient failures and terminal classification for &lt;code dir=&quot;auto&quot;&gt;404&lt;/code&gt; (“no such VM”) and RBAC &lt;code dir=&quot;auto&quot;&gt;403&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same calls from Claude or any other agent. No Xen-Orchestra-specific code on the client side.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;read-only-is-a-feature-not-a-gap&quot;&gt;Read-only is a feature, not a gap&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The natural framing — “252 beats 7” — is the wrong one. The two surfaces answer different questions.&lt;/p&gt;
&lt;p&gt;The first-party MCP server is read-only &lt;em&gt;on purpose&lt;/em&gt;. It runs in-process, ships with XO, needs no external infrastructure, and cannot break anything. If the question is &lt;em&gt;“let an assistant look at my infrastructure and answer questions about it,”&lt;/em&gt; that is the right shape, and you should use it. We are not trying to replace it — the DADL even includes a &lt;code dir=&quot;auto&quot;&gt;get_mcp_status&lt;/code&gt; tool that reports whether XO’s own built-in MCP server is enabled.&lt;/p&gt;
&lt;p&gt;The DADL answers a different question: &lt;em&gt;“let an agent operate my infrastructure — create, patch, snapshot, migrate, back up, provision RBAC — without handing it raw admin credentials.”&lt;/em&gt; That only makes sense with a control layer underneath. Through ToolMesh, every one of the 252 tools runs behind centralized credentials (the XO token never reaches the model), authorization (an agent can be granted &lt;code dir=&quot;auto&quot;&gt;list_vms&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;snapshot_vm&lt;/code&gt; but not &lt;code dir=&quot;auto&quot;&gt;delete_sr&lt;/code&gt;), and an audit log (every call recorded with what, when, and why). The write side is the whole point, and the governance layer is what makes the write side safe to expose.&lt;/p&gt;
&lt;p&gt;Both definitions live in the same registry. They are complements: read-only discovery in-process, full lifecycle management through a governed gateway.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;tracking-the-api-is-a-yaml-diff&quot;&gt;Tracking the API is a YAML diff&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;XO 6.4 added RBAC v2; 6.5 added &lt;code dir=&quot;auto&quot;&gt;PATCH /vms/{id}&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;/vm-controllers&lt;/code&gt;, and a REST snapshot-revert endpoint; 6.6 added scoped Storage and Network Administrator roles, writable backup repositories, and &lt;code dir=&quot;auto&quot;&gt;PATCH&lt;/code&gt; for VDIs and VIFs. Reflecting each of those in the DADL is the same small unit of work: open &lt;code dir=&quot;auto&quot;&gt;xen-orchestra.dadl&lt;/code&gt;, add the tool block (~30 lines of YAML per endpoint), run &lt;code dir=&quot;auto&quot;&gt;npm run validate&lt;/code&gt;, open a PR, review the diff, merge. ToolMesh picks the new tools up on next reload. No SDK changes, no release to cut, no npm publish, no “please upgrade” to send users.&lt;/p&gt;
&lt;p&gt;That is what every XO release looks like from here. 6.6 was seven new tool blocks and a version bump from 245 to 252 — an afternoon’s diff, not an event. The releases worth a second look are not the ones that add endpoints, but the ones that close a gap you had written down as a limitation.&lt;/p&gt;
&lt;p&gt;Two concrete examples from 6.6:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The release added &lt;code dir=&quot;auto&quot;&gt;PATCH /vdis/{id}&lt;/code&gt;. An earlier version of this post listed &lt;em&gt;“VDIs cannot be renamed via REST”&lt;/em&gt; under honest caveats — 6.6 closed that gap, and reflecting it was a single &lt;code dir=&quot;auto&quot;&gt;update_vdi&lt;/code&gt; tool block (rename a disk or grow it), annotated &lt;em&gt;“added in XO 6.6 — returns 404 on older builds”&lt;/em&gt; so an agent against an older host gets a clean terminal error instead of a silent failure.&lt;/li&gt;
&lt;li&gt;Backup repositories became writable: &lt;code dir=&quot;auto&quot;&gt;POST&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;PATCH&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;DELETE /backup-repositories&lt;/code&gt;. Three new tool blocks, the same shape as every other write endpoint, behind the same credentials, authorization, and audit — nothing about the gateway changed. The new built-in &lt;em&gt;Storage Administrator&lt;/em&gt; and &lt;em&gt;Network Administrator&lt;/em&gt; roles need no new tools at all; they compose out of the RBAC v2 surface the DADL already exposed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We made the general version of this argument &lt;a href=&quot;https://www.toolmesh.io/en/blog/dadl-the-end-of-mcp-server-boilerplate/&quot;&gt;in an earlier post&lt;/a&gt;; XO is the concrete case study for a write-heavy API.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-context-window-argument&quot;&gt;The context-window argument&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There is a second reason the tool count matters less than it looks: &lt;strong&gt;Code Mode&lt;/strong&gt;. Instead of injecting all 252 XO tool descriptions into the model’s context window, ToolMesh exposes two meta-tools (&lt;code dir=&quot;auto&quot;&gt;list_tools&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;execute_code&lt;/code&gt;). The model discovers what is available on demand and calls flat tool names from inside a sandboxed JavaScript runtime — &lt;code dir=&quot;auto&quot;&gt;toolmesh.xen_orchestra_list_vms&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;toolmesh.xen_orchestra_snapshot_vm&lt;/code&gt;, not a nested namespace.&lt;/p&gt;
&lt;p&gt;The token cost is roughly constant: ~1 k tokens of meta-tool description, whether the registry holds one backend with 7 tools or 31 backends with 4,132. A 252-tool backend adds zero tokens to every Claude conversation that does not actively touch Xen Orchestra. That is also why the JavaScript in the worked example above is not an illustration — it is literally what the model writes to call the tools.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;honest-caveats&quot;&gt;Honest caveats&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The DADL is not a universal win. Real limits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The SSE event stream is half-declarative.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;open_events_stream&lt;/code&gt; returns a subscription ID and the DADL describes the subscribe handshake, but the Server-Sent Events stream itself is a streaming protocol — DADL describes REST, not long-lived streams. For polling task state, &lt;code dir=&quot;auto&quot;&gt;get_task?wait=true&lt;/code&gt; long-polls cleanly; for a true real-time feed you are at the edge of what a declarative REST definition models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host power actions aren’t in the REST API.&lt;/strong&gt; Hosts have no REST route for &lt;code dir=&quot;auto&quot;&gt;start&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;shutdown&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;reboot&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;restart_toolstack&lt;/code&gt; — only &lt;code dir=&quot;auto&quot;&gt;disable&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;enable&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;management_reconfigure&lt;/code&gt; are exposed. For host power you use pool-level &lt;code dir=&quot;auto&quot;&gt;rolling_reboot&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;rolling_update&lt;/code&gt; or drop to the JSON-RPC API. We expose what exists; we cannot expose what does not. &lt;em&gt;(The entry that used to sit here — “VDIs cannot be renamed via REST” — was retired when 6.6 shipped &lt;code dir=&quot;auto&quot;&gt;PATCH /vdis/{id}&lt;/code&gt;.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDN traffic rules need a Premium plugin.&lt;/strong&gt; The six traffic-rule tools (&lt;code dir=&quot;auto&quot;&gt;add&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;delete&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;update_network_traffic_rule&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;add&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;delete&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;update_vif_traffic_rule&lt;/code&gt; — the two &lt;code dir=&quot;auto&quot;&gt;update&lt;/code&gt; variants new in 6.6) drive XO’s SDN Controller — a plugin bundled with XOA Premium, not part of the documented core REST API. Without it loaded the routes return &lt;code dir=&quot;auto&quot;&gt;404&lt;/code&gt;, so the agent gets a clean terminal error rather than a silent no-op. The other 246 tools work against a stock REST API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The write side needs privileges.&lt;/strong&gt; Full coverage assumes an admin user or an RBAC v2 user with the right &lt;code dir=&quot;auto&quot;&gt;acl-privileges&lt;/code&gt;. If you only need to &lt;em&gt;look&lt;/em&gt;, the read-only first-party server is less setup and a smaller blast radius — use it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Every call hops through ToolMesh.&lt;/strong&gt; For interactive operation this is invisible; for high-throughput batch jobs against XO, calling the REST API directly is faster.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DADL is good at one specific thing — making a REST API surface available to agents quickly, with a real control layer underneath. For Xen Orchestra, that surface happens to be 252 endpoints of mostly-mutating operations, which is exactly the case where a governance layer earns its keep.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-actually-different&quot;&gt;What is actually different&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The substantive question is not “DADL vs the first-party MCP server” — ToolMesh runs both, and they coexist in the same registry. The question is: &lt;strong&gt;what does it cost to give an agent the &lt;em&gt;write&lt;/em&gt; side of an API, safely?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a read-only server, the answer is “you don’t” — that is the deliberate trade. For a hand-coded read-write server, it costs an engineering cycle per endpoint: SDK shapes, handlers, tests, a release, an npm publish, user upgrades, plus the authorization and audit you have to build yourself. For a DADL behind ToolMesh, it costs one PR with a YAML diff, and the credentials, authorization, and audit come from the runtime.&lt;/p&gt;
&lt;p&gt;That is the asymmetry. Not “everyone should ditch the first-party server” — keep it for read-only discovery. Just: when you need an agent to actually operate Xen Orchestra, the unit of work is a YAML diff, and the safety is in the layer underneath rather than in withholding the write tools.&lt;/p&gt;</content:encoded><category>DADL</category><category>MCP</category><category>Xen Orchestra</category><category>Infrastructure</category></item><item><title>Anatomy of an Enterprise Tool Library</title><link>https://www.toolmesh.io/en/blog/anatomy-of-an-enterprise-tool-library/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/anatomy-of-an-enterprise-tool-library/</guid><description>How an Enterprise Tool Library actually works: the six pillars, the fail-closed request pipeline, and how DADL and Code Mode let one governed layer front any MCP server or REST API.

</description><pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most teams reach the same wall on the same Friday afternoon. A handful of MCP servers worked fine in development. Now there are fifteen, half of them touch production, and nobody can answer the basic questions — who called what, with which credential, against which policy, and where is the log. The problem is not any single server. It is that there is no shared layer underneath them.&lt;/p&gt;
&lt;p&gt;This post is about that layer. It describes how an &lt;strong&gt;Enterprise Tool Library&lt;/strong&gt; is built: the six concerns it has to merge into one runtime, the fail-closed pipeline a tool call walks through, and the two ideas — DADL and Code Mode — that make the whole thing scale to dozens of backends without bloating the agent’s context. For the business framing in German, see the companion piece on &lt;a href=&quot;https://www.dunkel.cloud/blog/enterprise-tool-library/&quot;&gt;dunkel.cloud (DE)&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-an-enterprise-tool-library-actually-is&quot;&gt;What an Enterprise Tool Library actually is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The term “MCP gateway” gets used for two very different things. One is a passthrough proxy that forwards tool calls to a single upstream server. The other is a governed execution layer that fronts every backend the organization owns. Only the second one is an Enterprise Tool Library.&lt;/p&gt;
&lt;p&gt;The distinguishing property is not the number of backends. It is structural. A library has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One authentication and authorization boundary.&lt;/strong&gt; Every tool call — no matter which upstream system it ends up hitting — passes the same identity check and the same per-tool access decision. A single source of truth for “who is allowed to call what.”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One coherent audit line.&lt;/strong&gt; Every call lands in the same store, with the same fields, queryable with one SQL query. Not “logs over here, MCP server logs over there, a CSV someone exported last quarter.”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One shared declarative description language.&lt;/strong&gt; REST APIs, MCP servers, internal services — all surfaced through the same definition format, so policy, retry, pagination, and credential injection are runtime concerns instead of bespoke wrapper code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A constant context footprint.&lt;/strong&gt; Adding the twentieth backend does not double the tokens the agent has to carry. The cost of “more tools” is paid by the library, not by every prompt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A single MCP server has none of these. A passthrough gateway has the first two at best, and only for one backend at a time. An Enterprise Tool Library is what you get when those four properties hold across the whole tool surface.&lt;/p&gt;
&lt;p&gt;ToolMesh is our open-source implementation of that shape. The rest of this post walks through how the parts fit together.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-six-pillars-as-mechanics&quot;&gt;The six pillars as mechanics&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh organizes the work into six pillars. Each one solves a concrete problem, lives in a specific package, and has a single config touchpoint.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-any-backend--two-ingress-paths-one-runtime&quot;&gt;1. Any Backend — two ingress paths, one runtime&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The library has to front anything the organization already runs. ToolMesh supports two ingress paths into the same execution pipeline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MCP backends&lt;/strong&gt; wrap existing MCP servers via HTTP or STDIO transport. If you already have a working server, you keep it; ToolMesh wraps it with the governance layer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DADL backends&lt;/strong&gt; describe REST APIs declaratively in YAML. No custom server code, no separate runtime — the description &lt;em&gt;is&lt;/em&gt; the integration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both paths register through the same internal &lt;code dir=&quot;auto&quot;&gt;backend.Register()&lt;/code&gt; interface and produce tools that look identical to the model. The backend type becomes an implementation detail; the security guarantees do not depend on it. Configuration lives in a single file:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;config/backends.yaml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;backends&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;github&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;github.dadl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;cloudflare&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;cloudflare.dadl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;legacy-internal&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;mcp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;transport&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;http&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;http://internal-mcp:8080&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Adding a backend is editing one file, not building a service.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-code-mode--flat-tool-surface-constant-context&quot;&gt;2. Code Mode — flat tool surface, constant context&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A naive MCP setup exposes every tool definition directly to the model. With one backend that is fine. With twenty it is ruinous: dozens of tool schemas, each with its own parameters and descriptions, all loaded into context before the agent has even read the user’s question. Around 50,000 tokens of pure tool metadata is a normal observation in the wild.&lt;/p&gt;
&lt;p&gt;ToolMesh sidesteps this entirely. The model sees two meta-tools — &lt;code dir=&quot;auto&quot;&gt;discover_tools&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;execute_code&lt;/code&gt; — and a compact TypeScript interface describing the available functions. That interface is roughly &lt;strong&gt;1,000 tokens&lt;/strong&gt; regardless of whether one or twenty backends are attached. The agent calls &lt;code dir=&quot;auto&quot;&gt;discover_tools&lt;/code&gt; once to see the type signatures, then writes JavaScript against them:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;issues&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;github_list_issues&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;owner: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DunkelCloud&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;repo: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ToolMesh&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;state: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;open&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;recent&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;issues&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;/span&gt;&lt;span&gt; =&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; - &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;created_at&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &amp;#x3C; &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;864e5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;recent&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; ({ number: &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;, title: &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt; }));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;execute_code&lt;/code&gt; runner parses that JavaScript with a Go AST walker, extracts each &lt;code dir=&quot;auto&quot;&gt;toolmesh.*&lt;/code&gt; call, and dispatches it through the normal pipeline. The model never executes arbitrary code against your infrastructure — it produces a script whose tool calls are statically extracted and authorized one by one. Code Mode is described in detail in &lt;a href=&quot;https://www.toolmesh.io/en/code-mode/&quot;&gt;/en/code-mode/&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-credential-store--runtime-injection-never-in-prompts&quot;&gt;3. Credential Store — runtime injection, never in prompts&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The concern most setups quietly ignore. A typical MCP configuration puts API keys in client configs, env vars on developer laptops, or — worst case — directly in the model’s context. None of those rotate well, none of those audit well, and any of them can leak into a transcript.&lt;/p&gt;
&lt;p&gt;ToolMesh references credentials by name in the DADL file and resolves them server-side at execution time. The model sees the tool interface and the filtered response. It never sees the token. The credential layer is pluggable through &lt;code dir=&quot;auto&quot;&gt;credentials.Register()&lt;/code&gt;:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Tier&lt;/th&gt;&lt;th&gt;Backend&lt;/th&gt;&lt;th&gt;Use case&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Embedded&lt;/td&gt;&lt;td&gt;Env vars (&lt;code dir=&quot;auto&quot;&gt;CREDENTIAL_*&lt;/code&gt;)&lt;/td&gt;&lt;td&gt;Local development, single-tenant&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Planned&lt;/td&gt;&lt;td&gt;Infisical&lt;/td&gt;&lt;td&gt;Centralized secret management&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Planned&lt;/td&gt;&lt;td&gt;HashiCorp Vault / OpenBao&lt;/td&gt;&lt;td&gt;Enterprise secret governance&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The embedded backend is the default and ships in the binary. The hosted backends are planned through the enterprise build tag — the registration pattern (inspired by Go’s &lt;code dir=&quot;auto&quot;&gt;database/sql&lt;/code&gt; drivers) is already in place, so adding a backend does not require touching the executor.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-openfga--rebac-honest-about-the-default&quot;&gt;4. OpenFGA — ReBAC, honest about the default&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Authorization in ToolMesh is relationship-based, powered by OpenFGA. The model is &lt;code dir=&quot;auto&quot;&gt;user → plan → tool&lt;/code&gt;: users belong to plans, plans have tool entitlements, and the executor asks OpenFGA whether the caller is allowed to invoke this specific tool before anything runs.&lt;/p&gt;
&lt;p&gt;Two modes ship today:&lt;/p&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;code dir=&quot;auto&quot;&gt;OPENFGA_MODE&lt;/code&gt;&lt;/th&gt;&lt;th&gt;Behavior&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;bypass&lt;/code&gt; (default)&lt;/td&gt;&lt;td&gt;No authorization checks. Dev-only.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;restrict&lt;/code&gt;&lt;/td&gt;&lt;td&gt;OpenFGA enforced on every tool call. Production.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The default is &lt;code dir=&quot;auto&quot;&gt;bypass&lt;/code&gt; so that &lt;code dir=&quot;auto&quot;&gt;docker compose up&lt;/code&gt; produces a working instance without forcing every contributor through OpenFGA setup. &lt;strong&gt;That is a deliberate developer-experience choice, not a security claim.&lt;/strong&gt; Production deployments switch to &lt;code dir=&quot;auto&quot;&gt;restrict&lt;/code&gt; and run &lt;code dir=&quot;auto&quot;&gt;./config/openfga/setup.sh&lt;/code&gt; to seed the model. Defaulting to bypass is honest about where the dev surface starts; the &lt;a href=&quot;https://www.toolmesh.io/en/authorization/&quot;&gt;Authorization&lt;/a&gt; page documents the production checklist.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-output-gate--deterministic-js-with-room-to-grow&quot;&gt;5. Output Gate — deterministic JS, with room to grow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Not every response should reach the model unchanged. Customer records contain PII. Internal systems return metadata the agent does not need. Error messages occasionally leak infrastructure details.&lt;/p&gt;
&lt;p&gt;Today, ToolMesh ships &lt;strong&gt;Layer 1&lt;/strong&gt; of the gate: deterministic JavaScript policies executed in the embedded &lt;a href=&quot;https://github.com/dop251/goja&quot;&gt;goja&lt;/a&gt; engine. A policy receives the response and the caller context and returns a filtered payload or a rejection:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;policies/redact-pii.js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;onResponse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;callerClass&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;trusted&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;redactEmails&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;redactPhones&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Policies are reviewable code committed to the repo, not black-box configuration. The gate sits on top of a pluggable evaluator registry (&lt;code dir=&quot;auto&quot;&gt;gate.RegisterEvaluator()&lt;/code&gt;) — additional layers are planned, including LLM-based content classification for compliance use cases. Today we ship one layer. We do not pretend to ship more. See &lt;a href=&quot;https://www.toolmesh.io/en/output-gate/&quot;&gt;/en/output-gate/&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;6-audit--slog-and-sqlite-sql-queryable&quot;&gt;6. Audit — slog and SQLite, SQL-queryable&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every tool call is recorded: who called it, which tool, with what parameters, what the result was, how long it took, and whether it succeeded. ToolMesh ships two audit backends — Go’s structured &lt;code dir=&quot;auto&quot;&gt;slog&lt;/code&gt; for log-style setups, and an append-only SQLite store for queryable compliance audits.&lt;/p&gt;
&lt;p&gt;When someone asks &lt;em&gt;“what did that agent do last Tuesday between 14:00 and 15:00?”&lt;/em&gt;, the answer is a &lt;code dir=&quot;auto&quot;&gt;SELECT&lt;/code&gt; query, not a forensic exercise across five log aggregators.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-fail-closed-request-pipeline&quot;&gt;The fail-closed request pipeline&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The pillars matter, but their value comes from the order they run in. A single tool call walks this path:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;┌─────────────────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│  Client (Claude Desktop / ChatGPT / CLI / hosted agent)         │&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└──────────────────────────────┬──────────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                               &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                               &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;┌───────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│ 1. Authentication     │   OAuth 2.1 PKCE / API key&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│    → UserContext      │   → identity established&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└──────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;┌───────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│ 2. Authorization      │   OpenFGA: user × plan × tool&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│    (fail closed)      │   → allowed or DENY&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└──────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;┌───────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│ 3. Credential         │   Resolved server-side,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│    Injection          │   never exposed to the model&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└──────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;┌───────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│ 4. Output Gate (in)   │   Optional pre-execution check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│    (fail closed)      │   → allowed or REJECT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└──────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;┌───────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│ 5. Execute            │   MCP server or REST via DADL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│                       │   → raw response&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└──────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;┌───────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│ 6. Output Gate (out)  │   PII redaction, shaping&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│                       │   → filtered response&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└──────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;┌───────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│ 7. Audit              │   Append-only, queryable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│                       │   → SQLite / slog&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└──────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                             &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Back to client&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The pipeline is &lt;strong&gt;fail-closed&lt;/strong&gt;: if AuthZ denies, nothing executes. If the gate rejects, nothing executes. If credential resolution fails, nothing executes. The default for every uncertainty is “do not run.” This is the inverse of how most ad-hoc setups behave, where a missing check silently means “proceed.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;callerclass--the-second-axis-nobody-else-has&quot;&gt;CallerClass — the second axis nobody else has&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most gateways treat authorization as a single axis: &lt;em&gt;who is the user?&lt;/em&gt; That is necessary but not sufficient. The same user, asking the same question, runs at a very different trust level when they are typing into a local Claude Desktop session versus when a hosted agent is making the call on their behalf as part of a scheduled workflow.&lt;/p&gt;
&lt;p&gt;ToolMesh treats this as a first-class concept: &lt;strong&gt;CallerClass&lt;/strong&gt;, attached to every request, with three values:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;CallerClass&lt;/th&gt;&lt;th&gt;Typical origin&lt;/th&gt;&lt;th&gt;Typical filtering&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;trusted&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Local CLI, developer laptop&lt;/td&gt;&lt;td&gt;Minimal — credentials redacted only&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;standard&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Authenticated user via known client&lt;/td&gt;&lt;td&gt;PII redaction, schema shaping&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;untrusted&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Hosted agents, CI bots, third-party clients&lt;/td&gt;&lt;td&gt;Strict — no admin tools, aggressive redaction&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;CallerClass modulates two things at once: which tools are reachable at all (an &lt;code dir=&quot;auto&quot;&gt;untrusted&lt;/code&gt; caller can be blocked from &lt;code dir=&quot;auto&quot;&gt;admin&lt;/code&gt;-level tools even if the user’s plan would normally allow them) and what the Output Gate redacts before the response reaches the model. Same backend, same user, different trust envelope — enforced automatically.&lt;/p&gt;
&lt;p&gt;This is the part that does not exist in passthrough gateways. They model the user. They do not model “who is asking on the user’s behalf.” For agent infrastructure, that second axis is where most of the real risk lives.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;worked-example-adding-a-backend&quot;&gt;Worked example: adding a backend&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Adding GitHub’s &lt;code dir=&quot;auto&quot;&gt;list_issues&lt;/code&gt; endpoint as a governed tool, in its entirety, is this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# github.dadl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;backend&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;github&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;base_url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://api.github.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;bearer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;credential&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;github_token&lt;/span&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;# resolved at runtime, never in prompt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;defaults&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;errors&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;retry_on&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;429&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;502&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;503&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;retry_strategy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;max_retries&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;backoff&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;exponential&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;tools&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;list_issues&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;GET&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/repos/{owner}/{repo}/issues&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;access&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;read&lt;/span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;# → mapped to plan entitlements via OpenFGA&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;List issues in a repository&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;owner&lt;/span&gt;&lt;span&gt;:    { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;repo&lt;/span&gt;&lt;span&gt;:     { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;:    { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;open&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;per_page&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;integer&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;pagination&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;strategy&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;link_header&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Twenty-three lines. Drop the file into the registry, reference it from &lt;code dir=&quot;auto&quot;&gt;backends.yaml&lt;/code&gt;, restart. What you get for free:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authentication&lt;/strong&gt; — OAuth or API key on the inbound side, bearer token injection on the outbound side, both handled by the runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization&lt;/strong&gt; — &lt;code dir=&quot;auto&quot;&gt;access: read&lt;/code&gt; slots the tool into the OpenFGA model; the request is denied before execution if the caller’s plan does not include it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credential isolation&lt;/strong&gt; — &lt;code dir=&quot;auto&quot;&gt;github_token&lt;/code&gt; is resolved from the credential store at execution time; the model never sees the value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retry and pagination&lt;/strong&gt; — declared in the description, executed by the runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit&lt;/strong&gt; — every call lands in the audit store with caller, parameters, status, latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output filtering&lt;/strong&gt; — the gate runs on the response before the model sees it, applying whatever policy applies to the caller’s class.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this is wrapper code anyone wrote for GitHub specifically. It is what the runtime does for every DADL-described tool. Building the same governance around a hand-rolled MCP server is several hundred lines of boilerplate per backend — work most teams ship without and regret later.&lt;/p&gt;
&lt;p&gt;The companion post &lt;a href=&quot;https://www.toolmesh.io/en/blog/dadl-the-end-of-mcp-server-boilerplate/&quot;&gt;Stop Rebuilding REST API Wrappers for MCP&lt;/a&gt; goes deeper on the DADL format and what disappears when you adopt it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-the-context-footprint-stays-constant&quot;&gt;Why the context footprint stays constant&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most gateways scale linearly with the number of backends — every new server adds tool definitions that ride along in every prompt. Twenty backends, twenty schema bundles, fifty thousand tokens before the user has typed a word.&lt;/p&gt;
&lt;p&gt;Code Mode breaks that scaling. The model sees &lt;code dir=&quot;auto&quot;&gt;discover_tools&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;execute_code&lt;/code&gt;, plus a compact TypeScript interface that lists what is available. Adding a backend grows the interface description, but not the per-request overhead — the agent looks up tools on demand instead of carrying them all the time.&lt;/p&gt;
&lt;p&gt;In practice, a ToolMesh instance fronting the current public &lt;a href=&quot;https://dadl.ai&quot;&gt;DADL registry&lt;/a&gt; — 31 APIs, 4,132 individual tools across GitHub, Cloudflare, GitLab, Stripe, Hetzner Cloud, Linode, NetBox, DeepL and others — surfaces to the model as roughly the same context cost as a single hand-built MCP server with a dozen tools would. That ratio is the entire point. Without it, the library shape does not work at all.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;scope-and-honesty-built-vs-planned&quot;&gt;Scope and honesty: built vs planned&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh is early-stage. Some of the architecture is shipped, some is in motion. Being explicit about that is more useful than being aspirational.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shipped today:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All six pillars wired through the fail-closed pipeline&lt;/li&gt;
&lt;li&gt;Authentication (OAuth 2.1 PKCE, API keys, multi-user via &lt;code dir=&quot;auto&quot;&gt;users.yaml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Authorization (OpenFGA, &lt;code dir=&quot;auto&quot;&gt;bypass&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;restrict&lt;/code&gt; modes)&lt;/li&gt;
&lt;li&gt;Credential store with embedded env-var backend&lt;/li&gt;
&lt;li&gt;Output Gate Layer 1 (deterministic goja JS policies)&lt;/li&gt;
&lt;li&gt;Audit via slog and SQLite&lt;/li&gt;
&lt;li&gt;MCP and DADL backends, registry-based&lt;/li&gt;
&lt;li&gt;Code Mode with AST-extracted tool calls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Deliberate developer defaults — switch off for production:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;OPENFGA_MODE=bypass&lt;/code&gt; (no authorization)&lt;/li&gt;
&lt;li&gt;Debug logging on&lt;/li&gt;
&lt;li&gt;HTTP+TLS reverse proxy assumed in front for TLS termination&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These defaults make &lt;code dir=&quot;auto&quot;&gt;docker compose up&lt;/code&gt; work cleanly in development. They are documented as not-production. The &lt;a href=&quot;https://www.toolmesh.io/en/configuration/&quot;&gt;Configuration&lt;/a&gt; page lists every variable and its production recommendation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Planned, registered but not yet shipped:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Infisical and HashiCorp Vault credential backends (extension point exists)&lt;/li&gt;
&lt;li&gt;Additional gate layers (LLM-based classification, compliance filters)&lt;/li&gt;
&lt;li&gt;Expanded DADL registry coverage (community contributions welcome)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Calling these “planned” is the right level of confidence. The registration mechanics are in place, the work to wire them in is ahead of us.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;backends-in-practice&quot;&gt;Backends in practice&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This article is the architectural reference. The detailed connector walkthroughs — how to populate NetBox from cloud APIs, how to put OPNsense behind a tool layer, how to integrate the HashiCorp stack — live in their own posts. The current installments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.toolmesh.io/en/blog/populating-netbox-from-real-infrastructure/&quot;&gt;Populating NetBox from Real Infrastructure with ToolMesh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;More to follow on OPNsense, the HashiCorp suite, and additional registry-driven integrations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you operate infrastructure that would make a good worked example, the &lt;a href=&quot;https://dadl.ai&quot;&gt;DADL registry&lt;/a&gt; is the place to start — most APIs that have OpenAPI specs can become DADL files in an afternoon.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;related&quot;&gt;Related&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The why, in German:&lt;/strong&gt; &lt;a href=&quot;https://www.dunkel.cloud/blog/enterprise-tool-library/&quot;&gt;dunkel.cloud — Enterprise Tool Library&lt;/a&gt; — business framing, not architecture&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The scaling trick:&lt;/strong&gt; &lt;a href=&quot;https://www.toolmesh.io/en/code-mode/&quot;&gt;/en/code-mode/&lt;/a&gt; — how the context footprint stays constant&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The protocol question:&lt;/strong&gt; &lt;a href=&quot;https://www.toolmesh.io/en/blog/why-mcp-gateways-alone-dont-solve-the-real-problem/&quot;&gt;Why MCP Gateways Alone Don’t Solve the Real Problem&lt;/a&gt; — gateway vs description layer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The description layer:&lt;/strong&gt; &lt;a href=&quot;https://www.toolmesh.io/en/blog/dadl-the-end-of-mcp-server-boilerplate/&quot;&gt;Stop Rebuilding REST API Wrappers for MCP&lt;/a&gt; — what DADL replaces&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Common questions:&lt;/strong&gt; &lt;a href=&quot;https://www.toolmesh.io/en/faq/&quot;&gt;/en/faq/&lt;/a&gt; — short answers across MCP, DADL, ToolMesh&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh&quot;&gt;GitHub — DunkelCloud/ToolMesh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connector format:&lt;/strong&gt; &lt;a href=&quot;https://dadl.ai&quot;&gt;dadl.ai&lt;/a&gt; — registry and spec&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;try-it&quot;&gt;Try it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The fastest path from this article to a running instance:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Hosted demo.&lt;/strong&gt; &lt;a href=&quot;https://www.toolmesh.io/en/demo/&quot;&gt;Try the Demo&lt;/a&gt; — a managed ToolMesh with a curated set of backends, no setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-host quickstart.&lt;/strong&gt; Clone the repo, &lt;code dir=&quot;auto&quot;&gt;docker compose up&lt;/code&gt;, point your MCP client at &lt;code dir=&quot;auto&quot;&gt;http://localhost:8080&lt;/code&gt;. The full walkthrough is in &lt;a href=&quot;https://www.toolmesh.io/en/getting-started/&quot;&gt;Getting Started&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;An Enterprise Tool Library is not a library of tools. It is the layer underneath them that makes “any backend” a property of the runtime instead of a property of every wrapper. The six pillars, the fail-closed pipeline, and the CallerClass axis are what turn a pile of MCP servers into something an enterprise can actually operate.&lt;/p&gt;</content:encoded><category>Architecture</category><category>MCP</category><category>DADL</category><category>Reference</category></item><item><title>DeepL v3 Customization Hub — Covered in One DADL</title><link>https://www.toolmesh.io/en/blog/deepl-v3-customization-hub-in-one-yaml-file/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/deepl-v3-customization-hub-in-one-yaml-file/</guid><description>DeepL just rolled out 13 new REST endpoints — style rules, multilingual glossaries, translation memory, /v3/languages, tag_handling v2. The official MCP server has zero of them today. We covered all of them in a 38 KB DADL. Here is the build log.

</description><pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In late May, DeepL turned on the &lt;a href=&quot;https://www.deepl.com/en/customization-hub&quot;&gt;Customization Hub&lt;/a&gt; — 13 new REST endpoints across style rules, multilingual glossaries, translation memory, the redesigned &lt;code dir=&quot;auto&quot;&gt;/v3/languages&lt;/code&gt; resource, and an opt-in &lt;code dir=&quot;auto&quot;&gt;tag_handling&lt;/code&gt; v2 algorithm. On the morning we merged our DADL for it, the official &lt;a href=&quot;https://github.com/DeepLcom/deepl-mcp-server&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;deepl-mcp-server&lt;/code&gt;&lt;/a&gt; still exposed nine tools — all of them v2 — and the community alternative &lt;a href=&quot;https://github.com/NimbleBrainInc/mcp-deepl&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;mcp-deepl&lt;/code&gt;&lt;/a&gt; was in the same shape, last released 2026-05-09. Not because anyone is slow. The unit of work for adding API coverage is fundamentally different on either side of that line, and the new release surfaces the gap clearly. This post is the build log.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-new-release-added&quot;&gt;What the new release added&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Customization Hub is the marketing name for an honest API expansion. DeepL now lets API consumers manage &lt;strong&gt;per-language style profiles&lt;/strong&gt; (think “Brand voice — DE”), &lt;strong&gt;multilingual glossaries&lt;/strong&gt; (one glossary, many &lt;code dir=&quot;auto&quot;&gt;source → target&lt;/code&gt; dictionaries), and &lt;strong&gt;translation memories&lt;/strong&gt; (segment-level reuse with a configurable match threshold). &lt;code dir=&quot;auto&quot;&gt;/v2/translate&lt;/code&gt; accepts three new parameters — &lt;code dir=&quot;auto&quot;&gt;style_id&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;custom_instructions&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;translation_memory_id&lt;/code&gt; — any of which silently bumps the model to &lt;code dir=&quot;auto&quot;&gt;quality_optimized&lt;/code&gt;. The new &lt;code dir=&quot;auto&quot;&gt;/v3/languages&lt;/code&gt; endpoint replaces the old &lt;code dir=&quot;auto&quot;&gt;/v2/languages&lt;/code&gt; and exposes per-resource feature flags (formality support, glossary support, style-rules support, voice). Finally, &lt;code dir=&quot;auto&quot;&gt;tag_handling_version: v2&lt;/code&gt; ships a much improved HTML/XML structure preservation algorithm — position-independent translation, better-preserved nesting.&lt;/p&gt;
&lt;p&gt;That is a meaningful expansion. None of it is in the official MCP server today.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-numbers&quot;&gt;The numbers&lt;/h2&gt;&lt;/div&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;DADL (&lt;code dir=&quot;auto&quot;&gt;deepl.dadl&lt;/code&gt;)&lt;/th&gt;&lt;th&gt;&lt;code dir=&quot;auto&quot;&gt;deepl-mcp-server&lt;/code&gt; (official)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Tools&lt;/td&gt;&lt;td&gt;&lt;strong&gt;27&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;9&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Coverage&lt;/td&gt;&lt;td&gt;text translation, document translation, multilingual v3 glossaries, style rules CRUD, custom instructions CRUD, TM listing, Write/rephrase, &lt;code dir=&quot;auto&quot;&gt;/v3/languages&lt;/code&gt;, usage&lt;/td&gt;&lt;td&gt;translate-text, rephrase-text, translate-document, list-glossaries, get-glossary-info, get-glossary-dictionary-entries, get-source-languages, get-target-languages, get-writing-styles&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Distribution&lt;/td&gt;&lt;td&gt;38 KB YAML&lt;/td&gt;&lt;td&gt;Node.js server (TypeScript SDK, build step, runtime to host)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Adding a new endpoint&lt;/td&gt;&lt;td&gt;~30 lines of YAML&lt;/td&gt;&lt;td&gt;Tool definition + request handler + tests + release&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;v3 Customization Hub coverage&lt;/td&gt;&lt;td&gt;full&lt;/td&gt;&lt;td&gt;none today&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The local validation, run from the registry checkout:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&gt; cd dadl-registry &amp;#x26;&amp;#x26; npm run validate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✅ deepl.dadl — 27 tools&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Source on GitHub: &lt;a href=&quot;https://github.com/DunkelCloud/dadl-registry/blob/main/deepl.dadl&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;dadl-registry/deepl.dadl&lt;/code&gt;&lt;/a&gt;. Merged in &lt;a href=&quot;https://github.com/DunkelCloud/dadl-registry/pull/34&quot;&gt;PR #34&lt;/a&gt;. The DADL spec itself: &lt;a href=&quot;https://dadl.ai/spec/dadl-spec-v0.2.md&quot;&gt;dadl.ai/spec&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-worked-example-style-rules-end-to-end&quot;&gt;A worked example: style rules end-to-end&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The interesting thing about style rules is that they actually take effect — &lt;code dir=&quot;auto&quot;&gt;custom_instructions&lt;/code&gt; carry through into the model output. Here is the round trip:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;deepl_create_style_rule&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Brand voice — DE&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;language: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;custom_instructions:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ label: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Greeting&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,     prompt: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Begin every translation with &apos;Sehr geehrte Damen und Herren&apos;.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ label: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Product term&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, prompt: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Render &apos;ToolMesh&apos; verbatim, never translate.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;toolmesh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;deepl_translate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;text:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;The deployment of ToolMesh was successful.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;source_lang: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;EN&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, target_lang: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DE&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;style_id: &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;style_id&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// → &quot;Sehr geehrte Damen und Herren, die Bereitstellung von ToolMesh war erfolgreich.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Two calls. No glue code. The first hits &lt;code dir=&quot;auto&quot;&gt;POST /v3/style_rules&lt;/code&gt;; the second references the result by &lt;code dir=&quot;auto&quot;&gt;style_id&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;POST /v2/translate&lt;/code&gt;. The DADL transparently handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the unusual &lt;code dir=&quot;auto&quot;&gt;DeepL-Auth-Key&lt;/code&gt; Authorization scheme (custom prefix, server-side credential injection — the model never sees the key);&lt;/li&gt;
&lt;li&gt;the language-code casing trap (UPPERCASE for &lt;code dir=&quot;auto&quot;&gt;translate&lt;/code&gt;, lowercase for style rules — both correctly typed);&lt;/li&gt;
&lt;li&gt;the silent model switch — passing &lt;code dir=&quot;auto&quot;&gt;style_id&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;custom_instructions&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;translation_memory_id&lt;/code&gt; flips &lt;code dir=&quot;auto&quot;&gt;model_type&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;quality_optimized&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;retries on &lt;code dir=&quot;auto&quot;&gt;429&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;529&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;Retry-After&lt;/code&gt;, and terminal classification for &lt;code dir=&quot;auto&quot;&gt;400&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;401&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;403&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;456&lt;/code&gt; (the Free-plan quota-exceeded code is &lt;code dir=&quot;auto&quot;&gt;456&lt;/code&gt;, not &lt;code dir=&quot;auto&quot;&gt;429&lt;/code&gt; — a footgun in hand-coded clients).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Same call from Claude or any other agent. No DeepL-specific code on the client side.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;maintenance-asymmetry&quot;&gt;Maintenance asymmetry&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the part that does not show up in the tool count but shapes how quickly an integration tracks an upstream API.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adding the v3 endpoints to the DADL:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;code dir=&quot;auto&quot;&gt;deepl.dadl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the new tool blocks (~30 lines of YAML per endpoint).&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;npm run validate&lt;/code&gt; — output is &lt;code dir=&quot;auto&quot;&gt;✅ deepl.dadl — 27 tools&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Open a PR. Review the YAML diff. Merge.&lt;/li&gt;
&lt;li&gt;ToolMesh picks the new tools up on next reload. Users see them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Adding the same endpoints to a hand-coded MCP server:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update the TypeScript SDK to model the new request and response shapes.&lt;/li&gt;
&lt;li&gt;Add tool definitions and request handlers (one per endpoint).&lt;/li&gt;
&lt;li&gt;Write or extend tests.&lt;/li&gt;
&lt;li&gt;Cut a release. Tag. Publish to npm.&lt;/li&gt;
&lt;li&gt;Tell users to upgrade. Wait for them.&lt;/li&gt;
&lt;li&gt;Repeat for the Python SDK if you maintain one. Repeat for any downstream forks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;DADL collapses steps 1, 2, 4, 5, and 6 into “edit one YAML file.” The PR diff is a reviewable change set, not a software release. We made the general version of this argument &lt;a href=&quot;https://www.toolmesh.io/en/blog/dadl-the-end-of-mcp-server-boilerplate/&quot;&gt;in an earlier post&lt;/a&gt;; the DeepL release is the concrete case study.&lt;/p&gt;
&lt;p&gt;The flip side worth naming: &lt;strong&gt;if you need behavior the runtime does not support, the YAML file does not save you.&lt;/strong&gt; DADL is declarative — there is no escape hatch for custom orchestration. For DeepL specifically, the document-translation flow (&lt;code dir=&quot;auto&quot;&gt;upload_document → check_document_status → download_document&lt;/code&gt;) is purely sequential REST and fits cleanly. A protocol-level deviation — say, the WebSocket Voice API — would not.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-context-window-argument&quot;&gt;The context-window argument&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The other reason the tool count matters less than it looks: &lt;strong&gt;Code Mode&lt;/strong&gt;. Instead of injecting every backend’s full tool list into the model’s context window, ToolMesh exposes two meta-tools (&lt;code dir=&quot;auto&quot;&gt;list_tools&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;execute_code&lt;/code&gt;). The model discovers what is available on demand and calls flat tool names from inside a sandboxed JavaScript runtime — &lt;code dir=&quot;auto&quot;&gt;toolmesh.deepl_translate&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;toolmesh.deepl_create_style_rule&lt;/code&gt;, not &lt;code dir=&quot;auto&quot;&gt;toolmesh.deepl.translate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The token cost is roughly constant: ~1 k tokens of meta-tool description, regardless of whether the registry has one backend with 9 tools or 31 backends with 4,132. Tool-count-driven bloat goes away. Adding the v3 Customization Hub took the DADL from 14 to 27 tools and added zero tokens to every Claude conversation that does not actively touch DeepL.&lt;/p&gt;
&lt;p&gt;In Code Mode, the JavaScript in the worked example above is not an illustration — it is literally what the model writes to call the tools. That is why it reads like JavaScript rather than like JSON tool-call payloads.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;honest-caveats&quot;&gt;Honest caveats&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The DADL is not a full coverage replacement. Real limits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Voice translation is WebSocket-based.&lt;/strong&gt; DeepL’s real-time voice translation is a bidirectional streaming protocol. DADL describes REST APIs declaratively; it does not describe streaming protocols. That endpoint is not in our 27-tool count and will not be — it is custom-server territory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Admin API is out of scope on purpose.&lt;/strong&gt; DeepL exposes admin endpoints (org analytics, developer-key management). We deliberately scoped this DADL to user-facing translation surfaces. Admin endpoints have a different threat model and belong in a separate, more privileged backend — not bundled with the everyday &lt;code dir=&quot;auto&quot;&gt;translate&lt;/code&gt; tool that any team member might use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Translation memory mutation is web-UI only.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;/v3/translation_memories&lt;/code&gt; is read-only via the API as of May 2026 — TMs are created and edited in the DeepL web UI. We expose &lt;code dir=&quot;auto&quot;&gt;list_translation_memories&lt;/code&gt;; we cannot expose endpoints that do not exist.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stateful MCP clients are a legitimate use case.&lt;/strong&gt; If you drive Claude Desktop, Cursor, or Continue directly and want a local MCP server with no governance layer in between, the official &lt;code dir=&quot;auto&quot;&gt;deepl-mcp-server&lt;/code&gt; is the right shape for that. The DADL-via-ToolMesh approach assumes a runtime that centralizes credentials, authorization, and audit — and that someone is willing to host it. For an individual developer with no governance requirements, that is overhead, not value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency-sensitive pipelines.&lt;/strong&gt; Every call hops through ToolMesh. For interactive translation at human latencies, this is invisible; for high-throughput batched pipelines, calling the DeepL client library directly is probably faster.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DADL is good at one specific thing — making REST API surface available to agents quickly, with a real control layer underneath. It is not a silver bullet, and it does not try to replace every kind of MCP server.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-actually-different&quot;&gt;What is actually different&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The substantive question is not “DADL vs MCP server” — that framing is wrong; ToolMesh runs both, and they coexist in the same registry. The substantive question is: &lt;strong&gt;what does it cost to track an upstream API release?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a hand-coded server, it costs an engineering cycle: SDK changes, tool definitions, handlers, tests, a release, an npm publish, user upgrades. That is why community MCP servers tend to lag fast-moving APIs — not for lack of effort, but because the work is linear in software releases, not in lines of YAML.&lt;/p&gt;
&lt;p&gt;For a DADL, it costs one PR with a YAML diff. DeepL shipped 13 new endpoints in mid-May. We shipped the updated DADL on May 21 — 14 old tools plus the 13 new ones, validated, merged, live. The work for the new endpoints was a couple of hours, most of it spent reading DeepL’s API reference and verifying behavior against a Free key. The runtime did the rest.&lt;/p&gt;
&lt;p&gt;That is the asymmetry. Not “everyone should ditch MCP servers.” Not “DADL is universally better.” Just: when the unit of work to add API coverage is a YAML diff rather than a software release, the integration tracks the upstream faster. That matters when the upstream is shipping a Customization Hub.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;→ Für die deutsche Geschäfts-Perspektive: &lt;a href=&quot;https://www.dunkel.cloud/de/blog/deepl-customization-hub-via-dadl/&quot;&gt;dunkel.cloud/de/blog/deepl-customization-hub-via-dadl&lt;/a&gt;&lt;/p&gt;</content:encoded><category>DADL</category><category>MCP</category><category>DeepL</category><category>Developer Experience</category></item><item><title>Populating NetBox from Real Infrastructure with ToolMesh</title><link>https://www.toolmesh.io/en/blog/populating-netbox-from-real-infrastructure/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/populating-netbox-from-real-infrastructure/</guid><description>How to use ToolMesh connectors to populate a fresh NetBox instance from Linode, Hetzner Cloud, Xen Orchestra and other sources — with a trust-building approach from import to autopilot.

</description><pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A typical scenario: three cloud providers, two hypervisors, a handful of edge sites. Somewhere around 200 devices and VMs, give or take. You spin up &lt;a href=&quot;https://netbox.dev/&quot;&gt;NetBox&lt;/a&gt;, stare at the empty dashboard, and realize the real work hasn’t started. Writing import scripts for five different APIs is a week of work you don’t want to do — so NetBox sits empty, and the spreadsheet wins again.&lt;/p&gt;
&lt;p&gt;This post shows how to skip that week. We walk through using ToolMesh to pull live infrastructure data from multiple sources and push it into NetBox via the NetBox DADL connector. For the business case and broader “why,” read the companion post on &lt;a href=&quot;https://dunkel.cloud/blog/populating-netbox-from-real-infrastructure&quot;&gt;dunkel.cloud&lt;/a&gt;. Here we focus on the how.&lt;/p&gt;
&lt;aside aria-label=&quot;Looking for the NetBox MCP server?&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Looking for the NetBox MCP server?&lt;/p&gt;&lt;div&gt;&lt;p&gt;Setup, examples, and the ready-to-use DADL definition: &lt;strong&gt;&lt;a href=&quot;https://www.toolmesh.io/en/integrations/netbox/&quot;&gt;NetBox MCP Server on toolmesh.io&lt;/a&gt;&lt;/strong&gt;, or the raw definition at &lt;strong&gt;&lt;a href=&quot;https://dadl.ai/d/netbox&quot;&gt;dadl.ai/d/netbox&lt;/a&gt;&lt;/strong&gt;. Drop it into ToolMesh and you have a NetBox MCP server with no custom code.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;architecture-overview&quot;&gt;Architecture Overview&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The data flow:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Hetzner Cloud ───┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Linode        ───┤&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Xen Orchestra ───┼── ToolMesh ── NetBox DADL ── NetBox API&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Unifi         ───┤&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Tailscale     ───┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each source system has its own API, its own authentication, and its own data model. ToolMesh acts as the gateway: it authenticates against each source using stored credentials, normalizes the responses, and writes the results into NetBox through the NetBox DADL definition.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.toolmesh.io/en/dadl/&quot;&gt;DADL&lt;/a&gt; (Declarative API Definition Language) is a YAML-based format that describes REST APIs as tool definitions. Instead of writing a custom MCP server wrapper for each API, you describe the endpoints, parameters, and authentication in one file. ToolMesh turns that description into callable tools at runtime — handling credential injection, authorization, and audit logging automatically. The NetBox DADL covers most of the NetBox &lt;a href=&quot;https://docs.netbox.dev/en/stable/integrations/rest-api/&quot;&gt;REST API&lt;/a&gt;; each source DADL covers one cloud or appliance. No custom glue code sits between them.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;mapping-source-objects-to-netbox-objects&quot;&gt;Mapping: Source Objects to NetBox Objects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The core challenge is translating between data models. A Hetzner “server” becomes a NetBox “device.” A Linode “instance” also becomes a device, but the field names differ. Here is how the main objects map:&lt;/p&gt;



























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Source&lt;/th&gt;&lt;th&gt;Source Object&lt;/th&gt;&lt;th&gt;NetBox Object&lt;/th&gt;&lt;th&gt;Key Fields&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Hetzner Cloud&lt;/td&gt;&lt;td&gt;Server&lt;/td&gt;&lt;td&gt;Device&lt;/td&gt;&lt;td&gt;name, server_type → device_type, datacenter → site&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hetzner Cloud&lt;/td&gt;&lt;td&gt;Network&lt;/td&gt;&lt;td&gt;Prefix&lt;/td&gt;&lt;td&gt;ip_range → prefix, name → description&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Linode&lt;/td&gt;&lt;td&gt;Instance&lt;/td&gt;&lt;td&gt;Device&lt;/td&gt;&lt;td&gt;label → name, type → device_type, region → site&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Linode&lt;/td&gt;&lt;td&gt;IPv4/IPv6&lt;/td&gt;&lt;td&gt;IP Address&lt;/td&gt;&lt;td&gt;address → address, linode_id → assigned device&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Xen Orchestra&lt;/td&gt;&lt;td&gt;VM&lt;/td&gt;&lt;td&gt;Virtual Machine&lt;/td&gt;&lt;td&gt;name_label → name, power_state → status&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Xen Orchestra&lt;/td&gt;&lt;td&gt;Network&lt;/td&gt;&lt;td&gt;VLAN&lt;/td&gt;&lt;td&gt;name_label → name, MTU&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Unifi&lt;/td&gt;&lt;td&gt;Device&lt;/td&gt;&lt;td&gt;Device&lt;/td&gt;&lt;td&gt;name, model → device_type, site → site&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Tailscale&lt;/td&gt;&lt;td&gt;Node&lt;/td&gt;&lt;td&gt;Device&lt;/td&gt;&lt;td&gt;hostname → name, addresses → IP Addresses&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Concretely, a single Hetzner server comes back looking like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;42891337&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;web-fra-01&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;server_type&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;cx22&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;cores&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;memory&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;datacenter&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;fsn1-dc14&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;location&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;fsn1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; } },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;public_net&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;ipv4&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;ip&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;95.217.xx.xx&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; } },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;status&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;running&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;After ToolMesh runs it through the NetBox DADL, that single source object produces three linked NetBox records: a &lt;code dir=&quot;auto&quot;&gt;Site&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;fsn1&lt;/code&gt;), a &lt;code dir=&quot;auto&quot;&gt;DeviceType&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;cx22&lt;/code&gt;, 2 cores, 4 GB), and a &lt;code dir=&quot;auto&quot;&gt;Device&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;web-fra-01&lt;/code&gt;) with a primary IP attached. The &lt;code dir=&quot;auto&quot;&gt;Site&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;DeviceType&lt;/code&gt; are created once and reused on every subsequent Hetzner server in the same datacenter.&lt;/p&gt;
&lt;p&gt;Mappings are not hardcoded. The field translation logic lives in the orchestration layer between the source DADL and the NetBox DADL. You can override mappings, add custom fields, or skip objects entirely — if your servers encode a role in the hostname (&lt;code dir=&quot;auto&quot;&gt;web-fra-01&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;db-fra-02&lt;/code&gt;), a small transform sets &lt;code dir=&quot;auto&quot;&gt;device_role&lt;/code&gt; accordingly. Dependencies are resolved automatically: prerequisite objects get created before the main record.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-trust-escalation-model&quot;&gt;The Trust Escalation Model&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Blindly syncing cloud inventory into a production NetBox is a reasonable thing to be nervous about. The approach we recommend is graduated trust — start with zero risk and escalate as confidence grows. None of this is a bespoke CLI; it all runs as natural-language prompts to Claude (or any agent) with ToolMesh connected. The examples below are the actual prompts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1: Initial Import (empty database).&lt;/strong&gt; Before any devices can be created, NetBox needs a minimal scaffold: at least one Site (devices must belong to one), a Manufacturer for device types, and ideally a Tenant for organizational grouping. Give Claude the facts about your infrastructure and let it set things up:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Set up NetBox for my company. Our tenant is “Acme GmbH”. I run infrastructure in two regions: Linode Frankfurt (&lt;code dir=&quot;auto&quot;&gt;eu-central&lt;/code&gt;) and Linode Amsterdam (&lt;code dir=&quot;auto&quot;&gt;nl-ams&lt;/code&gt;) — create those as Sites under a region called “Europe”. The Manufacturer for all Linode instances is “Akamai/Linode”. Once that’s in place, import all my Linode instances as Devices, create the matching Device Types (g6-standard-2, g6-standard-4, etc.) on the fly, and attach their public IPv4 and IPv6 addresses. NetBox is empty, so no dry-run needed — just do it and show me a summary when done.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Claude calls the Linode tools to list instances, the NetBox tools to create sites, tenant, manufacturer, device types, devices, and IPs in the right order. The summary at the end tells you how many of each were created.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2: Read-only Diff.&lt;/strong&gt; Once NetBox has data, the next check asks what would change without touching anything:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Compare my current Linode instances with what’s in NetBox. Don’t change anything — just tell me the differences in plain English.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The response is prose, not a machine diff:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I compared 23 Linode instances against the 22 devices in NetBox tagged &lt;code dir=&quot;auto&quot;&gt;source=linode&lt;/code&gt;. Four differences:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;New instance&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;db-ams-04&lt;/code&gt; exists at Linode (region &lt;code dir=&quot;auto&quot;&gt;nl-ams&lt;/code&gt;, type &lt;code dir=&quot;auto&quot;&gt;g6-standard-4&lt;/code&gt;, created yesterday) but isn’t in NetBox yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IP change&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;web-ams-01&lt;/code&gt; has a new primary IPv4 in NetBox (&lt;code dir=&quot;auto&quot;&gt;172.104.x.12&lt;/code&gt;) but Linode now reports &lt;code dir=&quot;auto&quot;&gt;172.104.x.47&lt;/code&gt;. Looks like it was rebuilt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Status drift&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;worker-ams-03&lt;/code&gt; is marked &lt;code dir=&quot;auto&quot;&gt;active&lt;/code&gt; in NetBox, but Linode reports it as &lt;code dir=&quot;auto&quot;&gt;offline&lt;/code&gt; (powered off 6 days ago).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Missing at source&lt;/strong&gt;: &lt;code dir=&quot;auto&quot;&gt;legacy-bastion-ams&lt;/code&gt; exists in NetBox but is no longer at Linode. Probably decommissioned — worth confirming before I touch it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Want me to apply 1–3 and flag 4 for your review?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is where most of the trust gets built. You see exactly what would happen, in language you can audit at a glance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3: Reconcile with Confirmation.&lt;/strong&gt; Apply the changes with a human in the loop on anything destructive:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Go ahead with 1, 2, and 3. For anything that deletes or marks a device as decommissioned, ask me first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Creates and updates happen straight away; the legacy bastion triggers a confirmation prompt (“Delete &lt;code dir=&quot;auto&quot;&gt;legacy-bastion-ams&lt;/code&gt; from NetBox, or mark it &lt;code dir=&quot;auto&quot;&gt;decommissioning&lt;/code&gt; and keep the record? [delete / decommission / skip]”) before anything irreversible happens. Destructive operations always require explicit confirmation, even once the workflow is automated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 4: Scheduled Sync.&lt;/strong&gt; Once the mapping is trustworthy, hand the workflow to a scheduled agent:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every hour, pull the current Linode state and reconcile it with NetBox. Apply additions and field updates automatically. For anything that would delete a device or change its status to decommissioned, open a ticket in our tracker instead of acting — I’ll review those manually.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ToolMesh runs the prompt on the schedule, the agent does the same work it did interactively, and destructive actions stay gated on human review. New servers appear in NetBox minutes after provisioning. Every tool call lands in the audit log, so you can always ask later &lt;em&gt;what&lt;/em&gt; got changed, &lt;em&gt;when&lt;/em&gt;, and &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://dadl.ai/d/netbox&quot;&gt;NetBox DADL connector&lt;/a&gt; covers most of the NetBox 4.x REST API. Source connectors for &lt;a href=&quot;https://dadl.ai/d/hetzner-cloud&quot;&gt;Hetzner Cloud&lt;/a&gt;, &lt;a href=&quot;https://dadl.ai/d/linode&quot;&gt;Linode&lt;/a&gt;, &lt;a href=&quot;https://dadl.ai/d/xen-orchestra&quot;&gt;Xen Orchestra&lt;/a&gt;, Unifi, and &lt;a href=&quot;https://dadl.ai/d/tailscale&quot;&gt;Tailscale&lt;/a&gt; are available today. If you run infrastructure that is not covered yet, a new DADL file takes minutes, not days — check the &lt;a href=&quot;https://dadl.ai/browse&quot;&gt;DADL registry&lt;/a&gt; for existing definitions or contribute on &lt;a href=&quot;https://github.com/DunkelCloud/dadl-registry&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The real payoff lands after the import. A populated NetBox is not just documentation — it becomes the substrate an AI agent can reason over. “Which servers in Falkenstein are running kernel 5.x and need a reboot window next Tuesday?” is a NetBox query plus a ToolMesh tool call, not a week of shell scripts. That is why we started here: NetBox is the map, ToolMesh is how agents read and redraw it safely.&lt;/p&gt;
&lt;p&gt;For the strategic angle, read the full analysis on &lt;a href=&quot;https://dunkel.cloud/blog/populating-netbox-from-real-infrastructure&quot;&gt;dunkel.cloud&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>NetBox</category><category>DADL</category><category>MCP</category><category>Tutorial</category><category>Infrastructure</category></item><item><title>Introducing ToolMesh — The Missing Control Layer for AI Agents</title><link>https://www.toolmesh.io/en/blog/introducing-toolmesh/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/introducing-toolmesh/</guid><description>AI agents are reaching production faster than governance can keep up. ToolMesh is the missing control layer between AI agents and enterprise systems — governing tool calls and connecting any API in minutes.

</description><pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;AI agents are reaching production systems faster than security and governance can keep up. Teams connect agents to CRMs, cloud APIs, internal databases, ticketing systems, and IoT devices — but the infrastructure between the agent and those systems is still held together with environment variables and good intentions.&lt;/p&gt;
&lt;p&gt;The typical setup looks like this: an MCP server has direct access to API keys, every connected agent gets full access to every tool, and there is no record of what was called, when, or by whom. That works for a prototype. It does not work when the agent talks to Stripe, your HR system, and your cloud provider in the same session.&lt;/p&gt;
&lt;p&gt;The problem compounds in enterprises. Organizations run dozens of backend systems across teams and departments. Each one needs its own integration, its own credentials, its own access policy. Without a central control layer, every new agent connection is a new unaudited trust relationship — and the attack surface grows with each one.&lt;/p&gt;
&lt;p&gt;ToolMesh is our answer to that gap.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-toolmesh-is&quot;&gt;What ToolMesh is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh is the missing control layer between AI agents and enterprise systems. It sits between AI agents and backend systems, turning uncontrolled tool calls into a governed, auditable process — and connecting any REST API or MCP server in minutes, not months.&lt;/p&gt;
&lt;p&gt;Every tool call flows through a strict pipeline: &lt;strong&gt;Auth → AuthZ → Credential Injection → Execution → Output Gate → Audit.&lt;/strong&gt; If any step does not explicitly allow the request, nothing happens. The model never sees raw secrets. Every call is logged. Output can be filtered before it reaches the agent.&lt;/p&gt;
&lt;p&gt;It runs as a single binary or Docker container. It is Apache 2.0 licensed. There is no SaaS dependency.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-six-pillars&quot;&gt;The six pillars&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh is built around six concerns that most agent setups handle inconsistently or not at all.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-authentication&quot;&gt;1. Authentication&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh supports OAuth 2.1 with PKCE for interactive clients like Claude Desktop, API keys for programmatic access, and multi-user setups via a &lt;code dir=&quot;auto&quot;&gt;users.yaml&lt;/code&gt; configuration. Every request is authenticated before it enters the pipeline.&lt;/p&gt;
&lt;p&gt;For quick starts, a single environment variable is enough. For production, you get per-user identity with roles, plans, and company context — without changing the architecture.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-authorization&quot;&gt;2. Authorization&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Access control is per-tool and per-user, powered by OpenFGA. DADL tools declare an &lt;code dir=&quot;auto&quot;&gt;access&lt;/code&gt; level — &lt;code dir=&quot;auto&quot;&gt;read&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;write&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;admin&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;dangerous&lt;/code&gt; — and policy files map those levels to roles. If the caller is not authorized for that specific tool, the request is denied before execution.&lt;/p&gt;
&lt;p&gt;ToolMesh also tracks CallerClass: which AI client triggered the request. A local Claude Code session can have different permissions than a hosted agent or a CI bot hitting the same API. Same backend, different trust levels, enforced automatically.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-credential-store&quot;&gt;3. Credential Store&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This is the concern most setups quietly ignore. In a typical MCP configuration, API keys sit in client configs or environment variables — visible to the model, scattered across machines, impossible to rotate centrally.&lt;/p&gt;
&lt;p&gt;ToolMesh injects credentials at runtime, server-side. The model sees the tool interface and the filtered result. It never sees the token, the key, or the session cookie. Credentials are referenced by name in the DADL file and resolved from the credential store at execution time.&lt;/p&gt;
&lt;p&gt;The default store reads from environment variables. The architecture supports pluggable backends for centralized secret management (planned: Infisical, HashiCorp Vault).&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;4-output-gate&quot;&gt;4. Output Gate&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Not every API response should reach the model unchanged. Customer records may contain PII. Internal systems may return metadata that is irrelevant or sensitive. Error messages may leak infrastructure details.&lt;/p&gt;
&lt;p&gt;The Output Gate runs JavaScript policies (via the goja engine) that can validate inputs before execution and filter outputs afterward. Use cases range from PII redaction to compliance filtering to rejecting dangerous inputs entirely. Policies are reviewable code, not black boxes.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;5-execution&quot;&gt;5. Execution&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh connects two types of backends:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DADL backends&lt;/strong&gt; describe REST APIs declaratively in YAML. A &lt;code dir=&quot;auto&quot;&gt;.dadl&lt;/code&gt; file defines endpoints, parameters, authentication, error handling, pagination, and retry logic. ToolMesh turns that description into MCP tools at runtime — no custom server code needed. The public &lt;a href=&quot;https://dadl.ai&quot;&gt;DADL registry&lt;/a&gt; currently includes definitions for 31 APIs covering 4,132 tools across providers like GitHub, Cloudflare, GitLab, Stripe, DeepL, Hetzner Cloud, and others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MCP server backends&lt;/strong&gt; connect existing MCP servers via HTTP or STDIO transport. If you already have a working MCP server, ToolMesh wraps it with the same authorization, credential, and audit pipeline.&lt;/p&gt;
&lt;p&gt;Both types go through the same fail-closed pipeline. The backend type is an implementation detail — the security guarantees are the same.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;6-audit-trail&quot;&gt;6. Audit Trail&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every tool call is recorded: who called it, which tool, with what parameters, what the result was, how long it took, and whether it succeeded. ToolMesh ships with two audit backends — structured logging via &lt;code dir=&quot;auto&quot;&gt;slog&lt;/code&gt; for simple setups, and an append-only SQLite store for queryable compliance audits.&lt;/p&gt;
&lt;p&gt;When someone asks “what did that agent do last Tuesday,” you can answer with a SQL query instead of a shrug.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;dadl-declare-instead-of-code&quot;&gt;DADL: declare instead of code&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The integration problem in agent tooling is not the protocol. MCP solved that. The problem is that every new API still requires a new MCP server — a new codebase, a new runtime, a new maintenance burden. Most teams stop after a handful of integrations because the per-API cost is too high.&lt;/p&gt;
&lt;p&gt;DADL (Declarative API Description Language) takes a different approach. Instead of writing a custom server for each API, you describe the API in YAML:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;backend&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;deepl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;base_url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://api.deepl.com/v2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;bearer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;credential&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;deepl_auth_key&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;tools&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/translate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;access&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Translate text into a target language&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;target_lang&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is a complete tool definition. ToolMesh handles authentication, parameter mapping, error handling, retries, and credential injection at runtime.&lt;/p&gt;
&lt;p&gt;Because DADL is compact and declarative, LLMs can generate working definitions from existing API documentation. Most of the early definitions in the registry were produced by Claude from API docs in under a minute, then reviewed and tuned. The format is designed to be AI-native: easy for machines to produce, easy for humans to review.&lt;/p&gt;
&lt;p&gt;We wrote a separate deep-dive on this: &lt;a href=&quot;https://www.toolmesh.io/en/blog/dadl-the-end-of-mcp-server-boilerplate/&quot;&gt;Stop Rebuilding REST API Wrappers for MCP&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;code-mode-scaling-that-otherwise-doesnt-work&quot;&gt;Code Mode: scaling that otherwise doesn’t work&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Connect 15 MCP servers to a single AI agent? Without ToolMesh, that simply does not work — the context window fills up, the client chokes. With ToolMesh, it is not a problem.&lt;/p&gt;
&lt;p&gt;Instead of exposing hundreds of individual MCP tools, ToolMesh exposes two meta-tools: &lt;code dir=&quot;auto&quot;&gt;list_tools&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;execute_code&lt;/code&gt;. The model gets a compact TypeScript interface description (~1,000 tokens instead of 50,000+) and writes JavaScript against it. One code block can chain multiple API calls in a single round-trip.&lt;/p&gt;
&lt;p&gt;That is the difference between “doesn’t work” and “just runs.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;self-hosted-open-source&quot;&gt;Self-hosted, open source&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;ToolMesh is Apache 2.0 licensed. The binary is self-contained — no external dependencies for the core feature set. &lt;code dir=&quot;auto&quot;&gt;docker compose up&lt;/code&gt; gets you a running instance with OAuth, audit logging, and a set of DADL backends.&lt;/p&gt;
&lt;p&gt;There is no cloud dependency. Your credentials, your audit logs, your policies — all on your infrastructure. The core is and will remain open source.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;get-started&quot;&gt;Get started&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The fastest path from zero to a working setup:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clone the repo and run &lt;code dir=&quot;auto&quot;&gt;docker compose up&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Configure your MCP client to point at ToolMesh&lt;/li&gt;
&lt;li&gt;Make your first tool call&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.toolmesh.io/en/getting-started/&quot;&gt;Getting Started guide&lt;/a&gt; walks through this in detail. The &lt;a href=&quot;https://www.toolmesh.io/en/architecture/&quot;&gt;Architecture overview&lt;/a&gt; explains how the pipeline works. The &lt;a href=&quot;https://dadl.ai&quot;&gt;DADL registry&lt;/a&gt; has ready-to-use definitions for 31 APIs.&lt;/p&gt;
&lt;p&gt;ToolMesh is on &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh&quot;&gt;GitHub&lt;/a&gt;. Issues, feedback, and DADL contributions are welcome.&lt;/p&gt;</content:encoded><category>announcement</category><category>ToolMesh</category><category>MCP</category><category>Security</category></item><item><title>Stop Rebuilding REST API Wrappers for MCP</title><link>https://www.toolmesh.io/en/blog/dadl-the-end-of-mcp-server-boilerplate/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/dadl-the-end-of-mcp-server-boilerplate/</guid><description>Even a stripped-down MCP wrapper is 90 lines of TypeScript. Production wrappers grow past 200. The same integration in DADL takes 40 lines of YAML — and the real gain is not the line count.

</description><pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every REST-to-MCP wrapper starts the same way. You pick an API, open the docs, and begin writing glue code. Authentication, parameter mapping, error handling, pagination, retries, response shaping. Two hundred lines later, you have one integration. Then you do it again for the next API.&lt;/p&gt;
&lt;p&gt;The real cost is not the 200 lines. It is reimplementing execution policy — credential handling, access control, audit logging — around every single wrapper. That work is expensive, repetitive, and difficult to scale.&lt;/p&gt;
&lt;p&gt;DADL takes a different approach: instead of writing a custom wrapper for every REST API, you describe the API declaratively. ToolMesh turns that description into tools and handles runtime concerns — including the ones most wrappers quietly ignore: credential isolation, authorization, and audit logging.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-wrapper-you-keep-rebuilding&quot;&gt;The wrapper you keep rebuilding&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is a minimal MCP server for a single DeepL translation endpoint in TypeScript. Stripped down — no tests, no retries, no pagination, no rate limiting:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Server } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;@modelcontextprotocol/sdk/server/index.js&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { StdioServerTransport } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;@modelcontextprotocol/sdk/server/stdio.js&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CallToolRequestSchema,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ListToolsRequestSchema,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;@modelcontextprotocol/sdk/types.js&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;API_KEY&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DEEPL_API_KEY&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;API_KEY&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DEEPL_API_KEY required&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Server&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ name: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;deepl-mcp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, version: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;1.0.0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ capabilities: { tools: {} } }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setRequestHandler&lt;/span&gt;&lt;span&gt;(ListToolsRequestSchema, &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; ({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tools: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;name: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Translate text into a target language. &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Supports up to 50 texts per request.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;inputSchema: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;properties: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;text: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;items: { type: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Array of texts to translate&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;target_lang: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Target language code (EN-US, DE, FR ...)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;source_lang: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Source language code. Omit for auto-detect&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;formality: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;enum: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;more&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;less&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;prefer_more&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;prefer_less&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;required: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;target_lang&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setRequestHandler&lt;/span&gt;&lt;span&gt;(CallToolRequestSchema, &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (request&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;Unknown tool: &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const { &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;target_lang&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;source_lang&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;formality&lt;/span&gt;&lt;span&gt; } =&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;arguments&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;target_lang&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (source_lang) body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;source_lang&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; source_lang;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (formality) body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;formality&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; formality;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://api.deepl.com/v2/translate&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;method: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;headers: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Authorization: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;DeepL-Auth-Key &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_KEY&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Content-Type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;application/json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;body: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(body)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;content: [{ type: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, text: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;DeepL error &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt; }],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;isError: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;content: [{ type: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, text: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(data, &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;) }],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;transport&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;StdioServerTransport&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;(transport);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is &lt;strong&gt;90 lines&lt;/strong&gt; for a single endpoint — already stripped to the minimum. No retries. No rate limit handling. No pagination. No structured error mapping. And the API key lives in an environment variable, where credentials are often distributed across client and server configs without centralized governance.&lt;/p&gt;
&lt;p&gt;A production-grade wrapper with multiple endpoints, proper error handling, retry logic, and rate limiting easily grows past &lt;strong&gt;200 lines&lt;/strong&gt; — plus a &lt;code dir=&quot;auto&quot;&gt;package.json&lt;/code&gt;, a build step, and a process to keep running.&lt;/p&gt;
&lt;p&gt;Now multiply that by every API you want to connect.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-same-integration-in-dadl&quot;&gt;The same integration in DADL&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the same DeepL translation tool described in DADL:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;backend&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;deepl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;rest&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;base_url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;https://api.deepl.com/v2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DeepL translation API&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;:                              &lt;/span&gt;&lt;span&gt;# ← credential injection handled by ToolMesh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;bearer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;credential&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;deepl_auth_key&lt;/span&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;#   references a server-side secret, never exposed to the model&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;prefix&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DeepL-Auth-Key &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;defaults&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;errors&lt;/span&gt;&lt;span&gt;:                          &lt;/span&gt;&lt;span&gt;# ← retry and error handling for all tools&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;retry_on&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;429&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;502&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;503&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;529&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;retry_strategy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;max_retries&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;backoff&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;exponential&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;initial_delay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;tools&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/translate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;access&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;# ← access classification for authorization&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Translate text into a target language&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;:                        &lt;/span&gt;&lt;span&gt;# ← parameter mapping: type, location, validation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;array&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Array of texts to translate&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;target_lang&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Target language code (EN-US, DE, FR)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;source_lang&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Source language. Omit for auto-detect&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;formality&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;default, more, less, prefer_more, prefer_less&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;40 lines of YAML.&lt;/strong&gt; No code. No build step. No custom server runtime to build and operate.&lt;/p&gt;
&lt;p&gt;But shorter is not the point. The point is what happens at runtime.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-control-layer-you-are-not-building&quot;&gt;The control layer you are not building&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most hand-rolled wrappers focus on one thing: making an API callable. They get the request out the door and the response back. That is necessary, but it is not sufficient for production.&lt;/p&gt;
&lt;p&gt;The harder questions are: Who is allowed to call this tool? Where do the credentials live? What happens to sensitive data in the response? Is there an audit trail?&lt;/p&gt;
&lt;p&gt;With a custom wrapper, those concerns either get bolted on after the fact or quietly ignored. With DADL and ToolMesh, they are the default:&lt;/p&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Concern&lt;/th&gt;&lt;th&gt;Custom MCP wrapper&lt;/th&gt;&lt;th&gt;DADL + ToolMesh&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Credentials&lt;/td&gt;&lt;td&gt;In client config, visible to the model&lt;/td&gt;&lt;td&gt;Injected server-side at runtime, never exposed&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Authorization&lt;/td&gt;&lt;td&gt;Typically none — full access for everyone&lt;/td&gt;&lt;td&gt;Per-tool, per-user access control&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Audit logging&lt;/td&gt;&lt;td&gt;Not built in&lt;/td&gt;&lt;td&gt;Every call logged and queryable&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Output filtering&lt;/td&gt;&lt;td&gt;Not built in&lt;/td&gt;&lt;td&gt;Policies can redact sensitive data before it reaches the model&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Retries&lt;/td&gt;&lt;td&gt;You implement backoff logic&lt;/td&gt;&lt;td&gt;Declared in &lt;code dir=&quot;auto&quot;&gt;retry_strategy&lt;/code&gt;, handled by runtime&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pagination&lt;/td&gt;&lt;td&gt;You build cursor/offset logic&lt;/td&gt;&lt;td&gt;Configured via &lt;code dir=&quot;auto&quot;&gt;pagination.strategy&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Error handling&lt;/td&gt;&lt;td&gt;You parse responses manually&lt;/td&gt;&lt;td&gt;Structured via &lt;code dir=&quot;auto&quot;&gt;errors.message_path&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Deployment&lt;/td&gt;&lt;td&gt;Build, package, run a process&lt;/td&gt;&lt;td&gt;Drop a &lt;code dir=&quot;auto&quot;&gt;.dadl&lt;/code&gt; file into ToolMesh&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;DADL describes what this API is. ToolMesh handles how calls to it are executed and controlled. That separation is the architectural shift that matters — not just the line count.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;you-do-not-write-it--your-llm-does&quot;&gt;You do not write it — your LLM does&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;DADL was designed to be AI-native. The format is compact, declarative, and close to the structure of existing API specifications.&lt;/p&gt;
&lt;p&gt;That means you can hand your LLM an OpenAPI spec, a Swagger definition, or even raw API documentation and ask it to produce a DADL file. In most cases, the result is usable on the first pass. Review it, adjust edge cases, deploy.&lt;/p&gt;
&lt;p&gt;Compare that to asking an LLM to generate a complete MCP server. The model has to produce working code — imports, type definitions, error handling, transport setup, build configuration. Every line is a potential bug. The output needs testing, debugging, and manual validation before it goes anywhere near production.&lt;/p&gt;
&lt;p&gt;With DADL, the LLM produces a data structure. If a field is wrong, you fix one line of YAML. If a parameter is missing, you add three lines. When something breaks, it is more often a reviewable spec issue than an opaque runtime exception buried in generated integration code.&lt;/p&gt;
&lt;p&gt;That is the real unlock. DADL is not just easier for humans to write. It is dramatically easier for AI to produce correctly.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-this-scales&quot;&gt;How this scales&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The DeepL example above covers a single endpoint. Real APIs have dozens.&lt;/p&gt;
&lt;p&gt;The full &lt;a href=&quot;https://dadl.ai&quot;&gt;DeepL DADL definition&lt;/a&gt; covers 13 tools — translate, document upload, document status, glossaries, text improvement, language lists, usage tracking — in under 500 lines of YAML. The &lt;a href=&quot;https://dadl.ai&quot;&gt;GitHub definition&lt;/a&gt; covers 202 tools: repositories, issues, pull requests, commits, code search, and more. Building that as a hand-coded MCP server is not a weekend project. It is a product.&lt;/p&gt;
&lt;p&gt;As of today, the &lt;a href=&quot;https://dadl.ai&quot;&gt;DADL registry&lt;/a&gt; contains 31 backend definitions with 4,132 tools across APIs like GitHub, Cloudflare, Hetzner Cloud, Linode, GitLab, and others. Each one is a single reviewable YAML file.&lt;/p&gt;
&lt;p&gt;As the number of tools grows, there is another scaling concern: context window overhead. ToolMesh addresses this with Code Mode, where the model works through two meta-tools — &lt;code dir=&quot;auto&quot;&gt;list_tools&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;execute_code&lt;/code&gt; — instead of carrying hundreds of tool definitions directly in context. That keeps tool discovery efficient regardless of how many backends are connected.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-dadl-does-not-do&quot;&gt;What DADL does not do&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;DADL is a declarative format for describing REST APIs. It is not a workflow engine, not a general-purpose programming language, and not a replacement for every kind of MCP server.&lt;/p&gt;
&lt;p&gt;If your integration needs custom business logic, complex multi-step orchestration, or non-REST protocols, a custom server is still the right choice. ToolMesh can work with those too — it connects both DADL-described backends and existing MCP servers.&lt;/p&gt;
&lt;p&gt;But for the large class of integrations that are fundamentally “call a REST API with the right auth and parameters” — and that is most of them — a custom wrapper is a poor default. It duplicates infrastructure concerns, spreads control logic across codebases, and creates maintenance work that adds no unique value.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-shift&quot;&gt;The shift&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The MCP ecosystem solved the protocol problem. Clients and hosts speak the same language.&lt;/p&gt;
&lt;p&gt;But protocol standardization did not make backend integration cheap. Every new API still meant a new server, a new codebase, a new runtime, a new maintenance burden. That is why most teams stop after a handful of integrations and the long tail of useful APIs stays disconnected.&lt;/p&gt;
&lt;p&gt;DADL changes the unit of work. Instead of “build a server,” the task becomes “describe the API.” Instead of a software project per integration, you get a reviewable YAML file per backend — executed through a runtime that handles security, credentials, and governance centrally.&lt;/p&gt;
&lt;p&gt;That is not just less code. It is less variance, less risk, and a fundamentally better default for connecting AI agents to real systems.&lt;/p&gt;
&lt;p&gt;Explore the &lt;a href=&quot;https://dadl.ai&quot;&gt;DADL registry&lt;/a&gt;, check out the &lt;a href=&quot;https://www.toolmesh.io/en/getting-started/&quot;&gt;ToolMesh documentation&lt;/a&gt;, or browse the source on &lt;a href=&quot;https://github.com/DunkelCloud/ToolMesh&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>DADL</category><category>MCP</category><category>Developer Experience</category></item><item><title>Why MCP Gateways Alone Don&apos;t Solve the Real Problem</title><link>https://www.toolmesh.io/en/blog/why-mcp-gateways-alone-dont-solve-the-real-problem/</link><guid isPermaLink="true">https://www.toolmesh.io/en/blog/why-mcp-gateways-alone-dont-solve-the-real-problem/</guid><description>Gateways are necessary for secure agent execution, but they do not solve the real scaling problem. The MCP era needs a description layer that makes backend creation trivial.

</description><pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;AI agents touching production systems need two things.&lt;/p&gt;
&lt;p&gt;First, they need a &lt;strong&gt;secure execution layer&lt;/strong&gt;: authentication, authorization, credential isolation, audit logging, output controls, and a runtime that fails closed when something is wrong.&lt;/p&gt;
&lt;p&gt;Second, they need a &lt;strong&gt;cheap way to expose backends&lt;/strong&gt;: not just one or two hand-built integrations, but dozens or hundreds of real systems that can become usable tools without turning every API into a miniature software project.&lt;/p&gt;
&lt;p&gt;Most current MCP discussions focus almost entirely on the first problem.&lt;/p&gt;
&lt;p&gt;That is understandable. The first problem is scary. If an agent can call production tools, the blast radius is real. Credentials leak. PII slips through. Audit trails go missing. One hallucinated call can become an actual incident.&lt;/p&gt;
&lt;p&gt;So yes, gateways matter.&lt;/p&gt;
&lt;p&gt;They are the missing control plane between the model and the systems that matter.&lt;/p&gt;
&lt;p&gt;But they do &lt;strong&gt;not&lt;/strong&gt; solve the deeper scaling problem.&lt;/p&gt;
&lt;p&gt;That problem sits one layer earlier.&lt;/p&gt;
&lt;p&gt;The real bottleneck is not how we proxy tool calls.&lt;/p&gt;
&lt;p&gt;The real bottleneck is that we still make backend creation far too expensive.&lt;/p&gt;
&lt;p&gt;That is why MCP gateways alone do not solve the real problem.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;mcp-solved-the-interface-problem&quot;&gt;MCP solved the interface problem&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;MCP matters because it gives the ecosystem a common way to discover and invoke tools. That is already a major step forward. It reduces one class of integration pain immediately: clients and hosts no longer need a custom protocol for every tool provider.&lt;/p&gt;
&lt;p&gt;That is real progress.&lt;/p&gt;
&lt;p&gt;But it is only one part of the stack.&lt;/p&gt;
&lt;p&gt;A protocol standard is not the same thing as a scalable integration model.&lt;/p&gt;
&lt;p&gt;HTTP did not eliminate the need to design APIs.
OpenAPI did not eliminate the need to build services.
Kubernetes did not eliminate the need to write good applications.&lt;/p&gt;
&lt;p&gt;In the same way, MCP does not eliminate the work required to expose real systems as useful, safe, maintainable tools.&lt;/p&gt;
&lt;p&gt;It standardizes invocation.
It does not make integrations cheap.&lt;/p&gt;
&lt;p&gt;That distinction is easy to miss in early demos because the first few tools are always the easiest ones. A simple GitHub wrapper. A lightweight Stripe example. One internal endpoint turned into a proof of concept.&lt;/p&gt;
&lt;p&gt;That is enough to make the protocol look like the whole story.&lt;/p&gt;
&lt;p&gt;It is not.&lt;/p&gt;
&lt;p&gt;The hard part begins when the question changes from &lt;em&gt;“Can we expose a tool?”&lt;/em&gt; to &lt;em&gt;“Can we expose fifty backends without building fifty small software products?”&lt;/em&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;gateways-solve-runtime-control-not-integration-economics&quot;&gt;Gateways solve runtime control, not integration economics&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A serious MCP deployment needs a runtime that sits between agents and backends.&lt;/p&gt;
&lt;p&gt;That runtime should verify the caller, enforce authorization, inject credentials at execution time, log every call, and apply output controls before sensitive data reaches the model or the user. It should fail closed. If a check fails, nothing runs.&lt;/p&gt;
&lt;p&gt;That is exactly what a gateway is for.&lt;/p&gt;
&lt;p&gt;And this is where ToolMesh fits: a self-hosted execution layer between agents and infrastructure, enforcing authorization, secret injection, audit logging, and output policies on every tool call. That layer is not optional if your agent touches production systems. It is the difference between a neat demo and a production architecture.&lt;/p&gt;
&lt;p&gt;But a gateway only governs what already exists.&lt;/p&gt;
&lt;p&gt;It can secure tool calls.
It can centralize policy.
It can make execution observable.&lt;/p&gt;
&lt;p&gt;What it cannot do, by itself, is change the cost structure of building the tools in the first place.&lt;/p&gt;
&lt;p&gt;That is the trap many teams are walking into right now.&lt;/p&gt;
&lt;p&gt;They improve the control layer and assume the scalability problem is solved.&lt;/p&gt;
&lt;p&gt;In reality, they have secured the top of the funnel while leaving the creation bottleneck untouched.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-real-nm-problem-is-upstream&quot;&gt;The real N×M problem is upstream&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;People often describe the N×M problem in MCP as a compatibility issue: many clients, many backends, too many custom pairings.&lt;/p&gt;
&lt;p&gt;MCP reduces part of that, which is good.&lt;/p&gt;
&lt;p&gt;But the more painful N×M problem lives upstream.&lt;/p&gt;
&lt;p&gt;It lives in the repeated work required to expose backend after backend after backend.&lt;/p&gt;
&lt;p&gt;For every API, teams end up rebuilding the same bundle of concerns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;auth handling&lt;/li&gt;
&lt;li&gt;parameter mapping&lt;/li&gt;
&lt;li&gt;pagination&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;error normalization&lt;/li&gt;
&lt;li&gt;schema shaping&lt;/li&gt;
&lt;li&gt;deployment packaging&lt;/li&gt;
&lt;li&gt;runtime ownership&lt;/li&gt;
&lt;li&gt;maintenance when the API changes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sometimes that work is wrapped in a custom MCP server.
Sometimes it becomes a thin adapter.
Sometimes it is disguised as a “small integration.”&lt;/p&gt;
&lt;p&gt;But it is the same pattern every time.&lt;/p&gt;
&lt;p&gt;That is why wrapper-heavy approaches feel fine at the beginning and terrible at scale.&lt;/p&gt;
&lt;p&gt;The first integration is exciting.
The fifth is manageable.
The twelfth is a prioritization problem.
By the twentieth, teams start exposing only the endpoints they absolutely need.&lt;/p&gt;
&lt;p&gt;Everything else falls into the backlog.&lt;/p&gt;
&lt;p&gt;The result is familiar: a landscape of partial wrappers, incomplete coverage, duplicated boilerplate, multiple runtimes, and a long tail of APIs that never become available to agents at all.&lt;/p&gt;
&lt;p&gt;That is not a gateway failure.
It is a backend creation failure.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;better-proxies-do-not-make-backend-creation-trivial&quot;&gt;Better proxies do not make backend creation trivial&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the core point.&lt;/p&gt;
&lt;p&gt;A better proxy can improve security, consistency, and observability.&lt;/p&gt;
&lt;p&gt;It cannot make a wrapper stop being a wrapper.&lt;/p&gt;
&lt;p&gt;If every new backend still begins with &lt;em&gt;“someone has to build and operate a custom MCP server”&lt;/em&gt;, the economics are still broken.&lt;/p&gt;
&lt;p&gt;You can put a beautifully engineered gateway in front of that world and it will absolutely improve production readiness.&lt;/p&gt;
&lt;p&gt;But the system will still scale badly because the unit of work is still too expensive.&lt;/p&gt;
&lt;p&gt;Each additional backend still means more code, more packaging, more deployment, more ownership, and more surface area to maintain.&lt;/p&gt;
&lt;p&gt;So the real question is not:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“How do we put a better proxy in front of MCP servers?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The real question is:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“How do we make exposing a backend so cheap that the long tail of useful systems actually gets connected?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That is the point where the conversation needs to shift.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-mcp-stack-is-missing-a-description-layer&quot;&gt;The MCP stack is missing a description layer&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once you separate the problem cleanly, the architecture becomes obvious.&lt;/p&gt;
&lt;p&gt;The MCP era needs three layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;protocol layer&lt;/strong&gt; for discovery and invocation&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;execution layer&lt;/strong&gt; for governance and runtime control&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;description layer&lt;/strong&gt; that makes backends cheap to expose&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first layer is MCP.
The second layer is the gateway.
The missing third layer is where most ecosystems are still immature.&lt;/p&gt;
&lt;p&gt;Without that third layer, every backend remains a custom engineering exercise.
With it, backend exposure becomes a declarative problem.&lt;/p&gt;
&lt;p&gt;And that changes everything.&lt;/p&gt;
&lt;p&gt;Because descriptions scale differently from code.&lt;/p&gt;
&lt;p&gt;A good description layer moves repeated logic out of bespoke wrappers and into shared runtime behavior. It turns backend exposure from a hand-built server into a compact, reviewable, declarative artifact.&lt;/p&gt;
&lt;p&gt;That is the architectural shift the MCP ecosystem needs if it wants to move beyond a handful of polished demos.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;dadl-is-the-missing-piece&quot;&gt;DADL is the missing piece&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is exactly where DADL matters.&lt;/p&gt;
&lt;p&gt;DADL, the Dunkel API Description Language, takes a different approach from wrapper-first integration. Instead of writing a custom MCP server for each REST API, the backend is described declaratively in YAML, while the runtime handles the standard mechanics that would otherwise be rebuilt again and again: authentication, pagination, retries, and error mapping.&lt;/p&gt;
&lt;p&gt;That is not just a nicer developer experience.
It is a different cost model.&lt;/p&gt;
&lt;p&gt;Without a description layer, the default unit of work is: &lt;strong&gt;build a server&lt;/strong&gt;.
With a description layer, the unit of work becomes: &lt;strong&gt;describe the backend, review it, and let the runtime execute it safely&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That is a radically better scaling curve.&lt;/p&gt;
&lt;p&gt;It means teams can expose more of the real API surface instead of stopping after a narrow “good enough” subset.
It means they do not need a separate runtime, dependency tree, Docker image, and maintenance burden for every integration.
It means common integration logic lives where it belongs: in one shared execution environment.&lt;/p&gt;
&lt;p&gt;And because DADL is intentionally compact and close to the structure of existing API specs, it also opens the door to something even more important: modern LLMs can often generate a usable first version from an existing API definition.&lt;/p&gt;
&lt;p&gt;That is where the category really changes.&lt;/p&gt;
&lt;p&gt;Because once the model can help generate the description, and the runtime can safely execute it, the cost of connecting a new backend drops by an order of magnitude.&lt;/p&gt;
&lt;p&gt;That is how you start to unlock the long tail.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-is-the-openapi-moment-for-mcp&quot;&gt;Why this is the OpenAPI moment for MCP&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The best analogy here is not “another gateway” or “another wrapper framework.”&lt;/p&gt;
&lt;p&gt;It is OpenAPI.&lt;/p&gt;
&lt;p&gt;OpenAPI did not replace HTTP. It made HTTP ecosystems programmable. It made APIs describable in a way that tools, docs, code generators, and platforms could all understand.&lt;/p&gt;
&lt;p&gt;That shift was not glamorous, but it changed the economics of API work.&lt;/p&gt;
&lt;p&gt;The MCP ecosystem needs the same kind of shift now.&lt;/p&gt;
&lt;p&gt;MCP already standardizes how tools are called.
What it still lacks, in many architectures, is a standard way to describe real backends so that tooling and runtime layers can do the boring work once instead of forcing teams to reimplement it forever.&lt;/p&gt;
&lt;p&gt;That is why DADL is best understood as &lt;strong&gt;OpenAPI for the MCP era&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Not because it replaces MCP.
Not because it replaces a gateway.
But because it fills the missing abstraction layer between raw backend APIs and secure, governable tool execution.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-developers-should-care&quot;&gt;Why developers should care&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For developers, this is mostly about escaping integration drudgery.&lt;/p&gt;
&lt;p&gt;Most wrapper code is not differentiated engineering. It is repetitive adaptation work that absorbs time, expands maintenance burden, and creates more runtime sprawl than value.&lt;/p&gt;
&lt;p&gt;A declarative backend layer changes that.&lt;/p&gt;
&lt;p&gt;Instead of spending engineering time on the same auth and pagination logic for the tenth time, teams can focus on what actually matters: domain behavior, workflows, guardrails, and the product logic built on top of those tools.&lt;/p&gt;
&lt;p&gt;Just as importantly, lower integration cost increases API coverage.&lt;/p&gt;
&lt;p&gt;That matters a lot.&lt;/p&gt;
&lt;p&gt;Agent systems often underperform not because the model is weak, but because the available tool surface is too thin. The backend may support fifty useful operations, but only six are exposed because wrapping the rest is too expensive.&lt;/p&gt;
&lt;p&gt;When backend creation becomes cheap, the tool layer can start to resemble the actual system instead of the budget constraints of the integration team.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-architects-should-care&quot;&gt;Why architects should care&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For architects, the value is even clearer.&lt;/p&gt;
&lt;p&gt;A three-layer model separates concerns in the right way.&lt;/p&gt;
&lt;p&gt;MCP handles communication.
The gateway handles trust, control, and auditability.
The description layer handles backend scale.&lt;/p&gt;
&lt;p&gt;That means policy stops depending on how a particular wrapper was written.
Credentials stay outside model context.
Audit becomes consistent.
Security becomes systemic instead of accidental.&lt;/p&gt;
&lt;p&gt;And new integrations can be added without creating a new snowflake service every time.&lt;/p&gt;
&lt;p&gt;That is the kind of architecture enterprises actually want.&lt;/p&gt;
&lt;p&gt;Not a zoo of semi-maintained MCP servers.
Not a future where every new backend implies another runtime to patch and observe.&lt;/p&gt;
&lt;p&gt;They want a stable execution layer and a cheap path for bringing more systems under that layer.&lt;/p&gt;
&lt;p&gt;That is exactly why gateways alone are not enough.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-future-is-not-gateway-versus-description-layer&quot;&gt;The future is not gateway versus description layer&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;It is gateway &lt;strong&gt;plus&lt;/strong&gt; description layer.&lt;/p&gt;
&lt;p&gt;This is not an either-or choice.&lt;/p&gt;
&lt;p&gt;You need MCP because the ecosystem needs a common protocol.
You need a gateway because production tool calls need governance.
And you need a description layer because no organization can afford to scale backend exposure through endless hand-built wrappers.&lt;/p&gt;
&lt;p&gt;That is the stack.&lt;/p&gt;
&lt;p&gt;MCP standardizes invocation.
ToolMesh secures execution.
DADL makes backend creation cheap enough to scale.&lt;/p&gt;
&lt;p&gt;Once you see the problem this way, the market noise gets easier to ignore.&lt;/p&gt;
&lt;p&gt;The winners in this space will not be the teams with the prettiest proxy story.
They will be the teams that solve both sides of the equation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;secure execution for real systems&lt;/li&gt;
&lt;li&gt;trivial creation for real backends&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-real-problem&quot;&gt;The real problem&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;So yes, build the gateway.
You need it.&lt;/p&gt;
&lt;p&gt;But do not mistake runtime control for integration scale.&lt;/p&gt;
&lt;p&gt;The real problem is not that MCP needs better proxies.
The real problem is that the ecosystem still treats backend exposure as custom software work when it should be a declarative operation.&lt;/p&gt;
&lt;p&gt;As long as that remains true, the long tail of useful systems will stay disconnected, and agent infrastructure will keep hitting the same ceiling.&lt;/p&gt;
&lt;p&gt;That ceiling does not break when we proxy harder.&lt;/p&gt;
&lt;p&gt;It breaks when backend creation becomes trivial.&lt;/p&gt;
&lt;p&gt;That is why MCP gateways alone do not solve the real problem.&lt;/p&gt;
&lt;p&gt;And that is why DADL matters.&lt;/p&gt;
&lt;p&gt;Not as a convenience feature.
Not as a sidecar format.
But as the missing description layer that turns secure agent tooling from a handcrafted practice into a scalable system.&lt;/p&gt;
&lt;p&gt;In that sense, the real breakthrough is not just safer tool calls.&lt;/p&gt;
&lt;p&gt;It is finally making backend creation cheap enough for the MCP era to scale.&lt;/p&gt;</content:encoded><category>AI</category><category>MCP</category><category>Architecture</category></item></channel></rss>