<?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>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 6.5&apos;s Full REST API — 245 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 6.4 stabilized its REST API and 6.5 added PATCH updates and VM controllers. Vates ships a first-party MCP server — 7 tools, read-only by design. We took a different cut: the entire 245-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. 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 245-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.5 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), networking (VIF/PIF/PBD), 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, 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;245&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, backups, 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 245-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 — 245 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/xen-orchestra.dadl&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;dadl-registry/xen-orchestra.dadl&lt;/code&gt;&lt;/a&gt;. Live in the registry at &lt;a href=&quot;https://dadl.ai/d/xen-orchestra&quot;&gt;dadl.ai/d/xen-orchestra&lt;/a&gt;. The DADL spec itself: &lt;a href=&quot;https://dadl.ai/spec/dadl-spec-v0.1.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 — “245 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 245 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. 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;Two concrete examples from the current release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;6.5 shipped &lt;code dir=&quot;auto&quot;&gt;POST /vms/{id}/actions/revert_snapshot&lt;/code&gt; for automatable snapshot revert. The DADL already carries it as &lt;code dir=&quot;auto&quot;&gt;revert_vm_to_snapshot&lt;/code&gt;, annotated &lt;em&gt;“requires XO 6.5+ (route returns 404 on older builds)”&lt;/em&gt; — so against an older host the agent gets a clean terminal error instead of a silent failure.&lt;/li&gt;
&lt;li&gt;6.5 also lays groundwork for a dedicated &lt;em&gt;VM administrator&lt;/em&gt; role. You do not have to wait for it: the DADL exposes the full RBAC v2 surface (&lt;code dir=&quot;auto&quot;&gt;create_acl_role&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;create_acl_privilege&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;attach_acl_role_to_user&lt;/code&gt;), so you can compose a least-privilege operator role today and grant an agent exactly the privileges it should hold — read on &lt;code dir=&quot;auto&quot;&gt;vm&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;host&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;pool&lt;/code&gt;, and nothing more.&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 245 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 27 backends with 3,338. A 245-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;VDIs cannot be renamed via REST.&lt;/strong&gt; There is no &lt;code dir=&quot;auto&quot;&gt;PATCH /vdis/{id}&lt;/code&gt; in the API — so there is no &lt;code dir=&quot;auto&quot;&gt;update_vdi&lt;/code&gt; tool. Rename via tags or drop to the JSON-RPC API. We expose what exists; we cannot expose what does not.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDN traffic rules need a Premium plugin.&lt;/strong&gt; The four traffic-rule tools (&lt;code dir=&quot;auto&quot;&gt;add&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;delete_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_vif_traffic_rule&lt;/code&gt;) 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 241 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 245 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; — 27 APIs, 3,338 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.1.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 27 backends with 3,338. 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 27 APIs covering 3,338 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 27 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 27 backend definitions with 3,338 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>