<?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: Rajeev Ramani</title>
    <description>The latest articles on DEV Community by Rajeev Ramani (@rajeevramani).</description>
    <link>https://dev.to/rajeevramani</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3674261%2F3de0f6c7-a7e4-49e8-b938-b65282990f88.png</url>
      <title>DEV Community: Rajeev Ramani</title>
      <link>https://dev.to/rajeevramani</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rajeevramani"/>
    <language>en</language>
    <item>
      <title>My Agent Did Everything Right, Then Gave Up</title>
      <dc:creator>Rajeev Ramani</dc:creator>
      <pubDate>Wed, 18 Feb 2026 00:42:52 +0000</pubDate>
      <link>https://dev.to/rajeevramani/my-agent-did-everything-right-then-gave-up-4n3h</link>
      <guid>https://dev.to/rajeevramani/my-agent-did-everything-right-then-gave-up-4n3h</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1589254065878-42c9da997008%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wzMDAzMzh8MHwxfHNlYXJjaHwyM3x8cm9ib3R8ZW58MHx8fHwxNzcxMzYyNjk4fDA%26ixlib%3Drb-4.1.0%26q%3D80%26w%3D1080" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1589254065878-42c9da997008%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wzMDAzMzh8MHwxfHNlYXJjaHwyM3x8cm9ib3R8ZW58MHx8fHwxNzcxMzYyNjk4fDA%26ixlib%3Drb-4.1.0%26q%3D80%26w%3D1080" title="yellow and black robot toy _Photo by Jason Leung on Unsplash_" alt="yellow and black robot toy" width="1080" height="720"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;In my &lt;a href="https://rajeevramani.substack.com/p/mcp-made-me-rethink-who-my-software" rel="noopener noreferrer"&gt;last post&lt;/a&gt;, I talked about the metadata problem — how deciding &lt;em&gt;what&lt;/em&gt; to expose through MCP was harder than the protocol itself. I wrote about the token tax, about how tool definitions eat context before the model does anything useful.&lt;/p&gt;

&lt;p&gt;I took that lesson to heart. Our MCP responses became minimal. {”ok”: true} with a reference to what was created. Lean. Token-efficient. Exactly what I thought agents needed.&lt;/p&gt;

&lt;p&gt;I was wrong about who I was optimising for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trace
&lt;/h2&gt;

&lt;p&gt;We’ve been testing two Python agents against Flowplane’s MCP server — an ops agent for diagnostics and a dev agent for deployments. I gave the dev agent a straightforward task:&lt;/p&gt;

&lt;p&gt;Deploy httpbin at localhost:8001 on path /v1/api/users -&amp;gt; /anything at port 10018&lt;/p&gt;

&lt;p&gt;Here’s what the agent did:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-&amp;gt; dev_preflight_check({”path”:”/v1/api/users”,”listen_port”:10018,”cluster_name”:”httpbin-svc”})&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OK: port available, no name conflicts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-&amp;gt; cp_create_cluster({”name”:”httpbin-svc”,”endpoints”:[{”address”:”localhost”,”port”:8001}]})&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OK: {”ok”:true,”ref”:{”type”:”cluster”,”name”:”httpbin-svc”,”id”:”a3f1...”}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-&amp;gt; cp_create_route_config({”name”:”httpbin-routes”,”virtual_hosts”:[&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{”name”:”httpbin-vhost”,”domains”:[”*”],”routes”:[&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{”name”:”httpbin-route”,”match”:{”prefix”:”/v1/api/users”},”route”:{”cluster”:”httpbin-svc”,”prefix_rewrite”:”/anything”}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;]}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;]})&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OK: {”ok”:true,”ref”:{”type”:”route_config”,”name”:”httpbin-routes”,”id”:”b7e2...”}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Three calls in, the agent had done everything right. Correct creation order, good naming, inline virtual hosts and routes in a single call. The reasoning was flawless.&lt;/p&gt;

&lt;p&gt;Then it fell apart.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-&amp;gt; cp_list_virtual_hosts({”route_config_id”:”b7e2...”})&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OK: [{”name”:”httpbin-vhost”,”id”:”c4d8...”}]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-&amp;gt; cp_get_virtual_host({”id”:”c4d8...”})&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OK: {”name”:”httpbin-vhost”,”domains”:[”*”],...}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-&amp;gt; cp_list_routes({”virtual_host_id”:”c4d8...”})&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OK: [{”name”:”httpbin-route”,”id”:”d9a1...”}]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-&amp;gt; cp_get_cluster({”name”:”httpbin-svc”})&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OK: {”name”:”httpbin-svc”,”endpoints”:[...]}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-&amp;gt; cp_list_listeners({”route_config”:”d9a1...”})&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OK: []&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Five verification calls. The agent was checking its own work — confirming that the virtual hosts, routes, and cluster it just created actually existed. It already knew they did. It created them. But it didn’t &lt;em&gt;trust&lt;/em&gt; the response enough to move on.&lt;/p&gt;

&lt;p&gt;Worse, look at that last call. It passed a route UUID where a route_config name was expected. Got zero results. And then it stopped. Never created the listener.&lt;/p&gt;

&lt;p&gt;End result: cluster exists, route config exists with proper virtual hosts and routes — all sitting in the database, correctly configured. But the agent concluded the deployment failed because it couldn’t verify its own work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wrong Kind of Efficient
&lt;/h2&gt;

&lt;p&gt;When I wrote about the token tax in the first post, I was thinking about tool &lt;em&gt;definitions&lt;/em&gt; — the schemas and descriptions that eat context before anything happens. So we made our &lt;em&gt;responses&lt;/em&gt; lean too. Create a route config with inline virtual hosts and routes? Here’s your confirmation:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{”ok”: true, “ref”: {”type”: “route_config”, “name”: “httpbin-routes”, “id”: “b7e2...”}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A human developer sees that and knows exactly what happened. They’ve read the docs, they understand the API contract, and they’ve written application code that handles this response based on that understanding. The response doesn’t need to be self-explanatory — the knowledge lives in the consuming application code, not in the payload.&lt;/p&gt;

&lt;p&gt;An agent has none of that. It sees ok: true and a reference to the top-level object. Were the inline virtual hosts created? How many routes ended up in the database? Is the route config ready to be attached to a listener, or does it need more configuration? The response doesn’t say.&lt;/p&gt;

&lt;p&gt;So the agent does what any reasonable system would do when it lacks confidence: it investigates. It calls cp_list_virtual_hosts to confirm they exist. It calls cp_get_virtual_host to check the details. It calls cp_list_routes to verify the routes landed. Each call burns tokens and introduces another point where things can go sideways — like passing a UUID where a name was expected. &lt;em&gt;The agent passed a route UUID to an endpoint that expected a route_config name — a consistency gap in our API surface that deserves its own post."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was optimising for the wrong consumer. Token-efficient responses are great when your consumer already has the mental model. &lt;em&gt;When your consumer is building the mental model from your responses alone, brevity becomes ambiguity&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Agent Actually Needed
&lt;/h2&gt;

&lt;p&gt;After studying the trace, the fix was straightforward. Not more data — more &lt;em&gt;relevant&lt;/em&gt; data:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“ok”: true,&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“ref”: {”type”: “route_config”, “name”: “httpbin-routes”, “id”: “b7e2...”},&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“created”: {&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“virtual_hosts”: 1,&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“routes”: 1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;},&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;“next_step”: “Create a listener referencing route_config ‘httpbin-routes’ with cp_create_listener”&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Three additions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirmation of nested effects.&lt;/strong&gt; The created field tells the agent that its inline virtual hosts and routes were actually persisted. No verification calls needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next step guidance.&lt;/strong&gt; The next_step field tells the agent what to do now. This sounds hand-holdy, but agents don’t have muscle memory. A DevOps engineer who’s deployed twenty services knows the listener comes next. An agent running this workflow for the first time — or the hundredth time with a blank context window — doesn’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Names, not just IDs.&lt;/strong&gt; Notice the next step says route_config ‘httpbin-routes’, not route_config ‘b7e2...’. Our agent knew the name “httpbin-routes” because it chose that name. But the response ecosystem kept handing back UUIDs, and the agent started using those instead. When it passed a UUID to an endpoint expecting a name, it got zero results and assumed failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verification Loop
&lt;/h2&gt;

&lt;p&gt;There’s a pattern here worth naming: the &lt;strong&gt;verification loop&lt;/strong&gt;. An agent creates a resource, gets a minimal acknowledgment, then spends 3-5 additional calls confirming what it just did. Each call costs tokens. Each call introduces a chance for ID/name confusion or hitting unexpected edge cases. And the information was available at creation time — we just didn’t return it.&lt;/p&gt;

&lt;p&gt;In our trace, the verification loop consumed more tokens than the actual deployment. Five GET calls to verify three creates. The agent was doing more reading than writing, and all of it was unnecessary.&lt;/p&gt;

&lt;p&gt;The fix isn’t to prevent agents from making verification calls. It’s to make them unnecessary. If your create response confirms what was created, includes the side effects, and points to the next step, the agent has no reason to look back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tension
&lt;/h2&gt;

&lt;p&gt;There’s a real tension here that I don’t think has a clean answer yet.&lt;/p&gt;

&lt;p&gt;Token efficiency says: return less. Every byte in the response is a byte the model has to process. Keep it lean.&lt;/p&gt;

&lt;p&gt;Agent confidence says: return more. Every ambiguity in the response triggers verification behaviour. The agent will spend those tokens anyway — either reading your response or making follow-up calls. Follow-up calls cost more.&lt;/p&gt;

&lt;p&gt;My current thinking: responses should be &lt;em&gt;informationally&lt;/em&gt; dense but &lt;em&gt;structurally&lt;/em&gt; simple. A flat created object with counts is cheaper than the agent making three list calls. A one-line next_step string is cheaper than the agent reasoning about workflow ordering from scratch. You’re not adding bloat — you’re preventing it downstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Didn’t Expect
&lt;/h2&gt;

&lt;p&gt;In the first post, I described a two-layer challenge: tool design (what to expose) and metadata design (how to describe it). I’d now add a third: &lt;strong&gt;response design&lt;/strong&gt; — what you send back after the tool runs.&lt;/p&gt;

&lt;p&gt;REST has conventions for status codes and resource representations. GraphQL lets clients specify what they want back. But neither tradition accounts for a consumer that needs to build confidence about what just happened and decide what to do next, all from a single response.&lt;/p&gt;

&lt;p&gt;API design for agents will become its own discipline. It borrows from REST, from GraphQL, from conversational UI design — but it’s not quite any of them. The consumer isn’t rendering a page or populating a cache. It’s making a decision. Your response is the input to that decision.&lt;/p&gt;

&lt;p&gt;We’re still early in figuring this out. The pattern I’ve landed on — confirmation of effects plus next-step guidance — works for our deployment workflows. Whether it generalizes, I don’t know yet. But the principle feels right: _ &lt;strong&gt;design your responses for the consumer that has to reason about them, not the one that already knows what they mean.&lt;/strong&gt; _&lt;/p&gt;

&lt;p&gt;If you're building MCP servers and running into similar patterns — or if you've found different solutions — I'd love to hear about it. You can find Flowplane at &lt;a href="https://github.com/rajeevramani/flowplane" rel="noopener noreferrer"&gt;github.com/rajeevramani/flowplane&lt;/a&gt;, or connect with me on &lt;a href="https://www.linkedin.com/in/rajeev-ramani/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>MCP Made Me Rethink Who My Software Serves</title>
      <dc:creator>Rajeev Ramani</dc:creator>
      <pubDate>Tue, 10 Feb 2026 01:13:02 +0000</pubDate>
      <link>https://dev.to/rajeevramani/mcp-made-me-rethink-who-my-software-serves-22f0</link>
      <guid>https://dev.to/rajeevramani/mcp-made-me-rethink-who-my-software-serves-22f0</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1720364760740-9ef772c63b86%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wzMDAzMzh8MHwxfHNlYXJjaHw5NHx8Y29ubmVjdGlvbnN8ZW58MHx8fHwxNzY5NTgwNzQ3fDA%26ixlib%3Drb-4.1.0%26q%3D80%26w%3D1080" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1720364760740-9ef772c63b86%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wzMDAzMzh8MHwxfHNlYXJjaHw5NHx8Y29ubmVjdGlvbnN8ZW58MHx8fHwxNzY5NTgwNzQ3fDA%26ixlib%3Drb-4.1.0%26q%3D80%26w%3D1080" title="A close up view of a green plant stem _Photo by notorious v1ruS on Unsplash_" alt="A close up view of a green plant stem _Photo by notorious v1ruS on Unsplash_" width="1080" height="1080"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;The Hard Part of MCP Isn’t the Protocol&lt;/p&gt;

&lt;p&gt;The Model Context Protocol is everywhere. Claude Desktop, Cursor, Windsurf—everyone’s racing to connect AI to tools.&lt;/p&gt;

&lt;p&gt;I spent the last few weeks adding MCP to &lt;a href="https://github.com/rajeevramani/flowplane" rel="noopener noreferrer"&gt;Flowplane&lt;/a&gt;. What started as “expose some endpoints to Claude” became something I didn’t expect: a complete rethink of who my software serves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Audiences, Two Servers
&lt;/h2&gt;

&lt;p&gt;MCP itself is simple—JSON-RPC 2.0, well-defined message types. I had it working in a day.&lt;/p&gt;

&lt;p&gt;The hard part wasn’t figuring out what to expose, but what and how much metadata would be needed to let an agent effectively use the platform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rajeevramani/flowplane" rel="noopener noreferrer"&gt;Flowplane&lt;/a&gt; manages Envoy proxies. My first instinct: wrap the infrastructure primitives—clusters, listeners, routes—in MCP tools. Ship it.&lt;/p&gt;

&lt;p&gt;Then I realised the MCP layer in Flowplane wasn’t serving &lt;em&gt;one&lt;/em&gt; kind of consumer — it had to serve two very different ones. A customer’s agent doesn’t want to “create a cluster with round-robin load balancing.” They want to call getUser(id: “123”). They don’t care about Envoy. They care about the APIs behind it. An internal DevOps agent, on the other hand, needs to create and manage APIs and their corresponding resources. I wasn’t just exposing tools — I was designing an AI-facing platform layer.&lt;/p&gt;

&lt;p&gt;So I decided to serve two different categories of tools.&lt;/p&gt;

&lt;p&gt;Control Plane tools for platform engineering agents who build the gateway—19 tools for managing clusters, listeners, routes, and filters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8rn2uydnntsvp57sutq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8rn2uydnntsvp57sutq.png" alt="Control Plane tools" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0z3bdmsfo857nxhw44a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0z3bdmsfo857nxhw44a.png" alt="Tools 2" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Gateway API tools for everyone else—dynamically generated from OpenAPI specs or learned from traffic. When you call these requests, they go through Envoy with the same JWT validation and rate limiting as any other client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2anplwp2atdr9rgpgonm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2anplwp2atdr9rgpgonm.png" alt="Tools 2" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feoc5wvvez6iyncw2by0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feoc5wvvez6iyncw2by0y.png" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h3&gt;
  
  
  The Metadata Problem
&lt;/h3&gt;

&lt;p&gt;MCP tools need good metadata—names, descriptions, schemas. Without them, AI can’t figure out which tool to use.&lt;/p&gt;

&lt;p&gt;The gap between a tool with an OpenAPI spec and a fallback generated from path patterns is brutal. One has rich descriptions and typed parameters. The other technically exists.&lt;/p&gt;

&lt;p&gt;I built two approaches to close this gap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OpenAPI extraction — When you import a spec, Flowplane pulls operationId, summaries, and request/response schemas automatically. Every route gets rich metadata immediately. Full confidence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schema learning — For APIs without specs, Flowplane observes traffic and infers schemas from actual requests and responses. Field types, required parameters, response shapes—all learned over time. Confidence grows with volume. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal: no route should ever be “not ready” for MCP. Whether you have perfect documentation or none at all, the tools should be usable.&lt;/p&gt;

&lt;p&gt;It’s not glamorous work. But it’s the difference between tools AI can use effectively and tools that just exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Token Tax
&lt;/h3&gt;

&lt;p&gt;Every MCP tool costs tokens before you use it.&lt;/p&gt;

&lt;p&gt;Tool definitions—names, descriptions, parameter schemas—live in the model’s context window. 19 Control Plane tools are manageable. Hundreds of Gateway API tools, one per endpoint, are not.&lt;/p&gt;

&lt;p&gt;With a large API surface, you’re spending context just describing what’s available. The model hasn’t done anything yet.&lt;/p&gt;

&lt;p&gt;I’m still figuring out the right approach—tool categories, lazy loading, trimming verbose schemas. Good metadata helps AI pick the right tool, but too much crowds out the actual conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The irony&lt;/strong&gt; : Good metadata helps AI pick the right tool, but too much crowds out the actual conversation. Right now, finding the balance feels more like art than science.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Point
&lt;/h3&gt;

&lt;p&gt;MCP isn’t complicated. Deciding what to expose is.&lt;/p&gt;

&lt;p&gt;The usual API questions still apply—audience, capabilities, permissions. But MCP adds a new one: what information does the model need to choose the right tool, and how do I surface it?&lt;/p&gt;

&lt;p&gt;That’s where I spent most of my time. The protocol took a day. The metadata problem is ongoing.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Everyone’s Building AI Apps. I Used AI to Build Infrastructure</title>
      <dc:creator>Rajeev Ramani</dc:creator>
      <pubDate>Sun, 04 Jan 2026 07:10:28 +0000</pubDate>
      <link>https://dev.to/rajeevramani/everyones-building-ai-apps-i-used-ai-to-build-infrastructure-4j8b</link>
      <guid>https://dev.to/rajeevramani/everyones-building-ai-apps-i-used-ai-to-build-infrastructure-4j8b</guid>
      <description>&lt;p&gt;If you’re paying attention to tech news, everyone’s building AI applications. RAG pipelines, agent frameworks, LLM wrappers. I went the other way.&lt;/p&gt;

&lt;p&gt;I used AI to build infrastructure.&lt;/p&gt;

&lt;p&gt;Specifically, I spent the last few months building a &lt;strong&gt;&lt;a href="https://github.com/rajeevramani/flowplane" rel="noopener noreferrer"&gt;control plane&lt;/a&gt;&lt;/strong&gt; for Envoy, the proxy that powers service meshes like Istio and sits behind most modern API gateways.&lt;/p&gt;

&lt;p&gt;Here’s what makes this interesting: I’d never touched Envoy’s xDS protocol before starting—complete novice. xDS is Envoy’s configuration protocol—a set of gRPC APIs (LDS, RDS, CDS, EDS, SDS) that let you dynamically configure the proxy without restarts. It’s powerful. It’s also dense protobuf that demands deep protocol knowledge.&lt;/p&gt;

&lt;p&gt;The thing that surprised me most was just how complex xDS actually is. Not conceptually—the ideas are straightforward. But the implementation details, the edge cases, the way resources reference each other. I’d underestimated it.&lt;/p&gt;

&lt;p&gt;So I leaned heavily on Claude Code. And when I say heavily, I mean 70%+ of Flowplane was AI-assisted. Not just autocomplete suggestions—actual architectural decisions, debugging sessions, test generation.&lt;/p&gt;

&lt;p&gt;What emerged is a control plane that lets you configure Envoy through REST APIs instead of writing raw protobuf. Create clusters, routes, listeners, filters—all through JSON that Flowplane translates into xDS. It handles JWT auth, OAuth2, rate limiting, header mutation, and 13 filter types total. Multi-tenant, team-scoped resources.&lt;/p&gt;

&lt;p&gt;The most interesting part is the learning engine. Point it at traffic flowing through Envoy, and it infers API schemas from observed request/response patterns. No documentation? Watch the traffic, learn the shape.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fww6ql8c7qrbkq2ydupzj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fww6ql8c7qrbkq2ydupzj.gif" alt="flowplane ui 1" width="600" height="395"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;The contrarian bet here isn’t Flowplane itself—it’s the approach. While everyone races to build the next AI application, there’s a whole layer of infrastructure that’s underserved. Envoy has millions of deployments. How many open source control planes exist for it? Not many.&lt;/p&gt;

&lt;p&gt;Open source matters. Envoy proved that a proxy could be a commodity. Control planes exist too—Istio, Kuma, Contour, Envoy Gateway—all open source, all free. But they’re Kubernetes-first. Envoy Gateway recently added experimental standalone support, but it’s still built around CRDs and the Gateway API. If you want to run Envoy standalone—as an edge gateway, on VMs, without K8s—you’re either writing xDS config by hand or paying for enterprise solutions.&lt;/p&gt;

&lt;p&gt;I’m not sure if Flowplane is the answer. But I’m increasingly convinced that AI doesn’t just help you build AI apps faster—it lets a single person tackle infrastructure problems that previously needed teams.&lt;/p&gt;

&lt;p&gt;What infrastructure is underserved in your stack? If you’re running Envoy outside Kubernetes, or just curious about the project, reach out on &lt;a href="https://github.com/rajeevramani/flowplane" rel="noopener noreferrer"&gt;github&lt;/a&gt; or find me on &lt;a href="https://www.linkedin.com/in/rajeev-ramani/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>devops</category>
      <category>networking</category>
    </item>
    <item>
      <title>I have trying to a solve a personal problem of configuring envoy dynamically and I think this may benefit others.</title>
      <dc:creator>Rajeev Ramani</dc:creator>
      <pubDate>Wed, 24 Dec 2025 03:28:10 +0000</pubDate>
      <link>https://dev.to/rajeevramani/i-have-trying-to-a-solve-a-personal-problem-of-configuring-envoy-dynamically-and-i-think-this-may-36k9</link>
      <guid>https://dev.to/rajeevramani/i-have-trying-to-a-solve-a-personal-problem-of-configuring-envoy-dynamically-and-i-think-this-may-36k9</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/rajeevramani" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3674261%2F3de0f6c7-a7e4-49e8-b938-b65282990f88.png" alt="rajeevramani"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/rajeevramani/configure-envoy-without-writing-protobuf-meet-flowplane-e3" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Flowplane: Configure Envoy with REST Instead of xDS&lt;/h2&gt;
      &lt;h3&gt;Rajeev Ramani ・ Dec 24 '25&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#tooling&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#networking&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#api&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>tooling</category>
      <category>devops</category>
      <category>networking</category>
      <category>api</category>
    </item>
    <item>
      <title>Flowplane: Configure Envoy with REST Instead of xDS</title>
      <dc:creator>Rajeev Ramani</dc:creator>
      <pubDate>Wed, 24 Dec 2025 01:35:44 +0000</pubDate>
      <link>https://dev.to/rajeevramani/configure-envoy-without-writing-protobuf-meet-flowplane-e3</link>
      <guid>https://dev.to/rajeevramani/configure-envoy-without-writing-protobuf-meet-flowplane-e3</guid>
      <description>&lt;p&gt;Envoy is powerful, but configuring it can be painful.&lt;/p&gt;

&lt;p&gt;If you've ever tried to set up a dynamic Envoy control plane, you know the drill: work with xDS protocol libraries like go-control-plane, construct deeply nested resource definitions, implement state management and versioning, figure out why your CDS update isn't being picked up. It's a lot of ceremony just to route traffic to a backend.&lt;/p&gt;

&lt;p&gt;I built Flowplane to skip all that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Flowplane?
&lt;/h2&gt;

&lt;p&gt;Flowplane is an open source control plane that exposes REST APIs for managing Envoy configuration. Instead of working with xDS libraries and nested structs, you POST JSON to create clusters, routes, and listeners. Flowplane handles the xDS protocol and serves configuration to your Envoy proxies via gRPC.&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;# Create a cluster&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/api/v1/clusters &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&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;'{
    "team": "platform",
    "name": "httpbin-cluster",
    "endpoints": [{"host": "httpbin.org", "port": 443}],
    "useTls": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No xDS library integration, no deeply nested resource construction required.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is Flowplane different from Envoy Gateway?
&lt;/h2&gt;

&lt;p&gt;If you're familiar with the Envoy ecosystem, you might be wondering how Flowplane compares to Envoy Gateway. They solve similar problems but take different approaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Envoy Gateway&lt;/strong&gt; is the official Envoy project for managing proxies. It implements the Kubernetes Gateway API, so you configure it with CRDs like &lt;code&gt;Gateway&lt;/code&gt; and &lt;code&gt;HTTPRoute&lt;/code&gt; via &lt;code&gt;kubectl apply&lt;/code&gt;. It's designed for teams already working in Kubernetes who think in those abstractions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flowplane&lt;/strong&gt; exposes Envoy's xDS concepts directly through REST. You're still working with clusters, listeners, and routes — the same mental model as Envoy — but through JSON and HTTP rather than Go/Python code or Kubernetes manifests.&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;Envoy Gateway&lt;/th&gt;
&lt;th&gt;Flowplane&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Config model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes Gateway API&lt;/td&gt;
&lt;td&gt;REST API (JSON)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary environment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes-native&lt;/td&gt;
&lt;td&gt;Runs anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;How you configure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl apply&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;curl&lt;/code&gt;, CLI, or Web UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-tenancy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;K8s namespace isolation&lt;/td&gt;
&lt;td&gt;Built-in team scoping&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Choose Envoy Gateway if:&lt;/strong&gt; You're on Kubernetes and want tight integration with the Gateway API ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Flowplane if:&lt;/strong&gt; You're running Envoy outside Kubernetes, want API-driven configuration for CI/CD integration, or prefer working with REST over YAML manifests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;Envoy is a battle-tested proxy that powers infrastructure at Lyft, Google, and Stripe. It's fast, extensible, and includes features other gateways charge for — OAuth2, JWT authentication, rate limiting, external authorization — all built in.&lt;/p&gt;

&lt;p&gt;The problem is the learning curve.&lt;/p&gt;

&lt;p&gt;Envoy's configuration is notoriously complex. You need to understand xDS protocols, work with verbose Go or Python libraries to construct resource definitions, and piece together listeners, clusters, and filter chains. For teams that just want to route traffic and secure their APIs, it's a steep hill to climb.&lt;/p&gt;

&lt;p&gt;There are existing open source projects that help you build Envoy control planes, but most still require working directly with xDS libraries and understanding protocol intricacies, or they assume you're running Kubernetes. If you're on ECS, VMs, or bare metal and just want an API gateway with OAuth2 and rate limiting, a full service mesh is overkill.&lt;/p&gt;

&lt;p&gt;I built Flowplane to bridge that gap — a RESTful abstraction that gives teams access to Envoy's powerful features without requiring Kubernetes or deep xDS expertise. POST some JSON, get a working proxy config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key features
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;REST API for everything&lt;/strong&gt; — Clusters, listeners, routes, filters, secrets. All manageable through standard HTTP endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLI support&lt;/strong&gt; — Prefer the command line? Flowplane includes a CLI for managing resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13 HTTP filters out of the box&lt;/strong&gt; — JWT auth, OAuth2, rate limiting, CORS, header mutation, external authorization. Configure them with JSON, not nested code structures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-tenant by default&lt;/strong&gt; — Resources are scoped to teams with token-based auth. Useful when multiple teams share proxy infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API schema learning&lt;/strong&gt; — This one's a bit different. Flowplane can capture traffic samples and infer JSON schemas from observed requests/responses. Handy for documenting APIs that don't have specs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web UI included&lt;/strong&gt; — A SvelteKit dashboard for managing resources if you prefer clicking over curling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Docker (Recommended)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; flowplane &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 50051:50051 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; flowplane_data:/app/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;FLOWPLANE_DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite:///app/data/flowplane.db &lt;span class="se"&gt;\&lt;/span&gt;
  ghcr.io/rajeevramani/flowplane:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Binary
&lt;/h3&gt;

&lt;p&gt;Download from &lt;a href="https://github.com/rajeevramani/flowplane/releases/tag/v0.0.11" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;:&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;# Linux (x86_64)&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://github.com/rajeevramani/flowplane/releases/download/v0.0.11/flowplane-x86_64-unknown-linux-gnu.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf flowplane-x86_64-unknown-linux-gnu.tar.gz

&lt;span class="c"&gt;# macOS (Apple Silicon)&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://github.com/rajeevramani/flowplane/releases/download/v0.0.11/flowplane-aarch64-apple-darwin.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf flowplane-aarch64-apple-darwin.tar.gz

&lt;span class="c"&gt;# Run&lt;/span&gt;
./flowplane-&lt;span class="k"&gt;*&lt;/span&gt;/flowplane
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Access points
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:8080/api/v1/" rel="noopener noreferrer"&gt;http://localhost:8080/api/v1/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swagger UI&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:8080/swagger-ui/" rel="noopener noreferrer"&gt;http://localhost:8080/swagger-ui/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xDS (gRPC)&lt;/td&gt;
&lt;td&gt;localhost:50051&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Flowplane is at v0.0.11 — functional but early. I'm using it for my own projects and looking for feedback from others who've felt the Envoy configuration pain.&lt;/p&gt;

&lt;p&gt;If you've struggled with Envoy configuration or want a simpler path to its features, give it a try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/rajeevramani/flowplane" rel="noopener noreferrer"&gt;github.com/rajeevramani/flowplane&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; Available in the repo under &lt;code&gt;/docs&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Star the repo, open an issue with feedback, or drop a comment here. I'd love to hear what's working and what's missing.&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>devops</category>
      <category>networking</category>
      <category>api</category>
    </item>
  </channel>
</rss>
