<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arun </title>
    <description>The latest articles on DEV Community by Arun  (@dev_dc9eca0093606c9a4162e).</description>
    <link>https://dev.to/dev_dc9eca0093606c9a4162e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4093624%2Fb267ed4f-c935-405f-876f-4c8a27a942e8.png</url>
      <title>DEV Community: Arun </title>
      <link>https://dev.to/dev_dc9eca0093606c9a4162e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dev_dc9eca0093606c9a4162e"/>
    <language>en</language>
    <item>
      <title>I Built 12 MCP Tools for PDF Generation — Here's How Product Engineers Use Them</title>
      <dc:creator>Arun </dc:creator>
      <pubDate>Sun, 20 Sep 2026 10:06:46 +0000</pubDate>
      <link>https://dev.to/dev_dc9eca0093606c9a4162e/i-built-12-mcp-tools-for-pdf-generation-heres-how-product-engineers-use-them-232i</link>
      <guid>https://dev.to/dev_dc9eca0093606c9a4162e/i-built-12-mcp-tools-for-pdf-generation-heres-how-product-engineers-use-them-232i</guid>
      <description>&lt;p&gt;Product engineers spend hours writing boilerplate to generate invoices, contracts, and reports. I was one of them. So I built an API that handles the entire pipeline — upload template, fill with data, return PDF — and exposed it as MCP tools so AI agents can call it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: docxtpl Breaks With Real Templates
&lt;/h2&gt;

&lt;p&gt;You have a Word template for invoices. It has a table with line items. You need to fill it with data from your API.&lt;/p&gt;

&lt;p&gt;You reach for &lt;code&gt;docxtpl&lt;/code&gt;. You throw in some Jinja2 syntax. And... it breaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;docxtpl&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DocxTemplate&lt;/span&gt;

&lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DocxTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.docx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acme Corp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Design&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Table collapses. XML is garbage.
&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice-filled.docx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The table collapses. The PDF is garbage. You Google "docx to pdf python" and find 47 Stack Overflow answers that all say "just use HTML templates."&lt;/p&gt;

&lt;p&gt;Here's why it breaks: Word's XML isn't HTML. A table row isn't a &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; tag. It's &lt;code&gt;&amp;lt;w:tr&amp;gt;&lt;/code&gt; wrapped in &lt;code&gt;&amp;lt;w:tbl&amp;gt;&lt;/code&gt;, with properties, cell definitions, and spans all entangled. When &lt;code&gt;docxtpl&lt;/code&gt; expands a &lt;code&gt;{% for %}&lt;/code&gt; loop across rows, it doesn't understand the XML boundaries. It copies text. The row structure breaks. LibreOffice throws an error.&lt;/p&gt;

&lt;p&gt;Most devs give up here. They either manually duplicate rows, switch to HTML templates and forget about Word, or accept that DOCX templates only work for flat documents without loops.&lt;/p&gt;

&lt;p&gt;I refused to accept any of those options.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Upload, Fill, Download
&lt;/h2&gt;

&lt;p&gt;I built a pipeline that handles the entire lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.docx upload → validate ZIP → extract schema → expand loops → render → convert to PDF → store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But here's the thing: you don't need to understand any of that. You just need three API calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Upload your template&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.docuqueue.com/templates/upload &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer dq_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@invoice.docx
&lt;span class="c"&gt;# Returns: { "template_id": "tpl_abc123", "fields": ["client", "items"] }&lt;/span&gt;

&lt;span class="c"&gt;# 2. Fill it with data&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.docuqueue.com/templates/tpl_abc123/fill &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer dq_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "client": "Acme Corp",
    "items": [
      {"name": "Design", "qty": 10, "rate": 150},
      {"name": "Development", "qty": 20, "rate": 175},
      {"name": "Review", "qty": 5, "rate": 120}
    ]
  }'&lt;/span&gt;
&lt;span class="c"&gt;# Returns: { "job_id": "job_xyz789", "status": "processing" }&lt;/span&gt;

&lt;span class="c"&gt;# 3. Download the PDF&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET https://api.docuqueue.com/jobs/job_xyz789/pdf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer dq_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; invoice-filled.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The XML manipulation, the loop expansion, the LibreOffice conversion — all handled. You get a PDF.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP Angle: 12 Tools for AI Agents
&lt;/h2&gt;

&lt;p&gt;I exposed this same pipeline as MCP tools. Twelve total, but here are the ones product engineers actually use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;th&gt;When You'd Use It&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upload_template&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upload a .docx or .html template&lt;/td&gt;
&lt;td&gt;Once, when you have a new template&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;See what fields the template expects&lt;/td&gt;
&lt;td&gt;Before filling, to validate data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fill_template&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fill template with data, get PDF&lt;/td&gt;
&lt;td&gt;Every time you generate a document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preview&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quick preview without full render&lt;/td&gt;
&lt;td&gt;When iterating on template design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_templates&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;See all your uploaded templates&lt;/td&gt;
&lt;td&gt;When you have many templates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check if a job is done&lt;/td&gt;
&lt;td&gt;For async generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;download_pdf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get the finished PDF&lt;/td&gt;
&lt;td&gt;After fill completes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delete_template&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove a template&lt;/td&gt;
&lt;td&gt;When cleaning up&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The workflow for an AI agent looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent: "Generate an invoice for Acme Corp with 3 line items"
  → upload_template(invoice.docx)
  → get_schema(template_id)
  → fill_template(template_id, {client: "Acme Corp", items: [...]})
  → download_pdf(job_id)
  → return PDF to user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent doesn't need to know about XML, LibreOffice, or any pipeline internals. It just uploads a template and gets a PDF back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication: OAuth 2.0 with PKCE
&lt;/h2&gt;

&lt;p&gt;Every tool call goes through OAuth 2.0 with PKCE. No API keys stored in code. Agents authenticate once, get a scoped token, and use it for all operations.&lt;/p&gt;

&lt;p&gt;This matters because you don't want an AI agent generating arbitrary documents from arbitrary templates without guardrails. Each token is scoped to the agent's permissions. If you only want it to fill invoices, that's all it can do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Under the Hood
&lt;/h2&gt;

&lt;p&gt;The pipeline handles edge cases you'd hit in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ZIP validation.&lt;/strong&gt; Users upload corrupted files, renamed .doc files, or PDFs with .docx extensions. I check the ZIP structure before touching anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loop expansion.&lt;/strong&gt; The custom step that makes docxtpl work. Walks the XML tree, finds loop constructs in table rows, clones them to match your data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual storage.&lt;/strong&gt; Recent previews go into Redis with a 1-hour TTL (instant re-preview). Permanent copies go to Cloudflare R2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit refund on failure.&lt;/strong&gt; If generation fails, the credit goes back atomically with an audit log. No stale balances.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you're building something that needs to generate PDFs from templates, check out &lt;a href="https://docuqueue.com" rel="noopener noreferrer"&gt;DocuQueue&lt;/a&gt;. There's a free tier that covers most small use cases.&lt;/p&gt;

&lt;p&gt;The MCP tools are available if you want AI agents to interact with the pipeline directly. Upload a template, call the fill tool, get a PDF. That's the whole API.&lt;/p&gt;

&lt;p&gt;The DOCX problem isn't solved perfectly. No one has solved it perfectly. But the row expansion trick makes it work reliably for the vast majority of real-world templates. And that's enough to build on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this useful? I'm &lt;a href="https://dev.to/arun"&gt;@arun&lt;/a&gt; on here. The code behind DocuQueue is a constant source of "why does this work this way" blog posts. More coming soon.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>pdf</category>
      <category>programming</category>
    </item>
    <item>
      <title>75% fewer tokens per Claude Code request. Same result.</title>
      <dc:creator>Arun </dc:creator>
      <pubDate>Tue, 25 Aug 2026 11:08:55 +0000</pubDate>
      <link>https://dev.to/dev_dc9eca0093606c9a4162e/i-was-paying-to-send-27-unused-tools-to-claude-so-i-filtered-them-48k1</link>
      <guid>https://dev.to/dev_dc9eca0093606c9a4162e/i-was-paying-to-send-27-unused-tools-to-claude-so-i-filtered-them-48k1</guid>
      <description>&lt;p&gt;&lt;em&gt;I stared at my Anthropic bill. $200. For one month. Of Claude Code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Not because I was doing anything special. Just... regular coding. But here's the thing nobody tells you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code sends 27 tool definitions with every single request.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;27 JSON schemas. Every turn. Even if you're just fixing a typo in a CSS file.&lt;/p&gt;

&lt;p&gt;I was paying to send GitHub tools, Context tools, IDE tools, agent tools... when all I needed was &lt;code&gt;Read&lt;/code&gt;, &lt;code&gt;Edit&lt;/code&gt;, and &lt;code&gt;Grep&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I built a proxy. A dumb, simple proxy that sits between my editor and the API. It reads my message, figures out what tools I actually need, and strips the rest before it reaches the model.&lt;/p&gt;

&lt;p&gt;Cost went from $200 to $50. Same code. Same quality.&lt;/p&gt;

&lt;p&gt;Here's how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Every AI coding tool works the same way. They have a bunch of built-in tools. File reading, bash execution, web search, git operations, context providers, IDE integration.&lt;/p&gt;

&lt;p&gt;And they send &lt;strong&gt;all of them&lt;/strong&gt; to the LLM on every request.&lt;/p&gt;

&lt;p&gt;Claude Code has 27 tools. Cursor has similar. Windsurf too.&lt;/p&gt;

&lt;p&gt;When you say "fix the nav labels in pricing.html", here's what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your client builds a request with 27 tool schemas&lt;/li&gt;
&lt;li&gt;That's ~1,200 tokens of JSON definitions&lt;/li&gt;
&lt;li&gt;The LLM reads all 27, picks 1 (usually &lt;code&gt;Edit&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;You pay for the other 26 doing nothing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's like printing a 100-page menu when you're ordering a coffee.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Proxy Actually Does
&lt;/h2&gt;

&lt;p&gt;One thing. It filters tool definitions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client sends 27 tools
    ↓
Proxy reads your message
    ↓
Proxy figures out what tools you need (6)
    ↓
Proxy forwards only those 6
    ↓
Provider responds normally
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No magic. No LLM calls. Just regex matching and list filtering.&lt;/p&gt;

&lt;h3&gt;
  
  
  The classification
&lt;/h3&gt;

&lt;p&gt;The proxy looks at your message and matches keywords:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix the nav labels in pricing.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;file_edit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;file_edit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  
&lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;    &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;file_edit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;file_edit &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it sends only the tools for that category:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KEEP: Read, Edit, Grep, Glob, Bash, Write
DROP: 21 others (GitHub, IDE, Context7, etc.)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITHOUT proxy:
  27 tools  ████████████████████████████████  ~1,200 tokens

WITH proxy:
   6 tools  ██████████░░░░░░░░░░░░░░░░░░░░░░  ~300 tokens

Saved: ~900 tokens per request (75%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same model. Same quality. Just fewer schemas.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone&lt;/span&gt;
git clone https://github.com/dvcoolarun/dynamic-tool-proxy
&lt;span class="nb"&gt;cd &lt;/span&gt;dynamic-tool-proxy

&lt;span class="c"&gt;# Install&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi httpx uvicorn

&lt;span class="c"&gt;# Run&lt;/span&gt;
python3 standalone_proxy.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point your editor at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code&lt;/span&gt;
&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8090 claude

&lt;span class="c"&gt;# Or set it permanently&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8090
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No API keys. No cloud accounts. No config files.&lt;/p&gt;

&lt;p&gt;The proxy listens on port 8090. Your editor thinks it's talking to Anthropic. The proxy strips the tools and forwards the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Categories It Knows
&lt;/h2&gt;

&lt;p&gt;The proxy classifies your message into one of 7 categories:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Tools Sent&lt;/th&gt;
&lt;th&gt;When It Triggers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;file_edit&lt;/td&gt;
&lt;td&gt;Read, Edit, Grep, Glob, Bash, Write&lt;/td&gt;
&lt;td&gt;"fix", "change", "edit", file extensions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;search&lt;/td&gt;
&lt;td&gt;Read, Grep, Glob, ListDirectory&lt;/td&gt;
&lt;td&gt;"find", "search", "where is"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;git&lt;/td&gt;
&lt;td&gt;Bash, Read, Grep, Glob&lt;/td&gt;
&lt;td&gt;"commit", "push", "branch", "git"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;debug&lt;/td&gt;
&lt;td&gt;Read, Grep, Bash, Glob&lt;/td&gt;
&lt;td&gt;"error", "bug", "why", "broken"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;web&lt;/td&gt;
&lt;td&gt;WebFetch, WebSearch, Read&lt;/td&gt;
&lt;td&gt;"url", "http", "website"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;arch&lt;/td&gt;
&lt;td&gt;Read, Grep, Glob, Bash, Agent&lt;/td&gt;
&lt;td&gt;"refactor", "structure", "design"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;All tools&lt;/td&gt;
&lt;td&gt;Ambiguous messages (fallback)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ambiguous messages get all tools. No quality loss. Just in case.&lt;/p&gt;

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

&lt;p&gt;I tracked it for a week. Here's what I saw:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tokens per request&lt;/td&gt;
&lt;td&gt;1,200&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests per day&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens saved per day&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;90,000&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost saved&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$150&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 100 requests per day, that's 2.7 million tokens per month you're not paying for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Works With Everything
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Clients:&lt;/strong&gt; Claude Code, Cursor, Windsurf, Cline, OpenCode, anything that uses Anthropic format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Providers:&lt;/strong&gt; Anthropic API, AWS Bedrock, Google Vertex, OpenAI-compatible, anything that accepts Anthropic format.&lt;/p&gt;

&lt;p&gt;You don't need a specific provider. Point it at whatever you're already using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Use RTK?
&lt;/h2&gt;

&lt;p&gt;RTK (if you're using it) solves a different problem:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Dynamic Tool Proxy&lt;/th&gt;
&lt;th&gt;RTK&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it filters&lt;/td&gt;
&lt;td&gt;Tool definitions (schemas)&lt;/td&gt;
&lt;td&gt;Tool outputs (results)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When it filters&lt;/td&gt;
&lt;td&gt;Before request sends&lt;/td&gt;
&lt;td&gt;After tool runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Savings&lt;/td&gt;
&lt;td&gt;~12k tokens per request&lt;/td&gt;
&lt;td&gt;Varies by output size&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They're complementary. Use both for maximum savings.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;1. Most developers don't check their token usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They see the bill and accept it. They don't realize they're paying to send unused schemas to the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The biggest win is boring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's just filtering a list. No LLM calls. No complex logic. Just "do you need this tool? no? remove it."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Local-first wins for developer tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers want control. They want to see what's happening. They don't want another SaaS account with another API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Regex is enough&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I thought I'd need LLM classification. I don't. Regex catches 90% of cases. Ambiguous messages fall back to full tool set. No quality loss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/dvcoolarun/dynamic-tool-proxy" rel="noopener noreferrer"&gt;https://github.com/dvcoolarun/dynamic-tool-proxy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT license. Free forever. Your data stays local.&lt;/p&gt;

&lt;p&gt;Star it if this saves you money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions?
&lt;/h2&gt;

&lt;p&gt;Drop a comment. I'll answer every one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Edit: Added streaming support. The proxy now passes through SSE streams unchanged.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
