Filtering MCP Tools with jq

Mar 21 2026

Last week we were attaching an MCP server to everything; this week we’re throwing them all out and replacing them with CLI’s (Command Line Interfaces). I think this may have all started when Open Claw creator, Peter Steinberger, was on the Lex Friedman podcast and took a subtle dig at MCP by saying “Screw MCP’s. Every MCP would be better as a CLI”. He’s not wrong and he goes on to explain one of the issues with MCP. If you ask an agent using an MCP for weather information about the current temperature, it might call a tool called current_conditions. That tool likely returns a lot of information: apparent temperature, relative humidity, wind speed, visibility… I’m only interested in temperature, but all of that other information ends up in my context window, just burning up precious tokens. You might also be inclined to fix this by making a lot of tools that return small amounts of data. Sadly that results in a lot of tools with similar names and the problem of selecting the right tool becomes the point of frustration. By the way, smarter people have written more elegantly about these tradeoffs. Back in November, Anthropic talked about these tradeoffs in Introducing advanced tool use on the Claude Developer Platform.

So why do people think CLI’s would be better? Well, if you use Claude Code much, you are very well aware that it has an absolute mastery of the unix command line. Rarely does it issue a shell command that is not piped into grep, or jq, or something else that filters or transforms the data from one command into exactly what is needed to to answer the question. When Steinberger says “MCP isn’t composable,” this is what he’s getting at. There isn’t an ecosystem that allows you to send the output of one MCP tool to another tool to be transformed before it ends up in the context window. Even if there was such an ecosystem, it would not be well represented in the training data. Unix shell kung-fu, however, is a skill that seems to be baked into the weights of these models. You don’t even have to waste space in the system prompt, the model just knows this stuff.

Wait. The future is … command line UNIX!?

I don’t think it is, personally. I know MCP has tradeoffs, and I’ve certainly been a critic of MCP, but I also think it has some virtues. Here’s its primary virtue, in my opinion. If you move outside of the realm of software engineering, the relevant information (context) is not centralized as it is in source code, it resides behind REST API’s. The dream of AI is to ask an agent a question and have it rummage unrestricted through the vast collection of REST API’s you, as a knowledge worker, typically access through web UI’s. You, as a knowledge worker, know that the hardest part of using all of those SaaS web products is logging in. Love it or hate it, MCP offers a realistic path for non-engineers to connect their favorite AI tool to all of those API’s. For the most part, MCP is just the dumb new transport through which AI will end up connecting to REST API’s. OAuth may be a pain, but your marketing folks are probably going to have an easier time with that than setting up a sandboxed virtual machine to allow an agent unfettered access to a lot of command line tools. MCP may have some issues, but it does offer a realistic onramp to connecting all your data sources to an AI. I just don't know what that kind of onboarding looks like for CLI agents. So what’s the solution? I have no idea. But maybe there are stupid ways we can work around the limitations of MCP. I offer you one such stupid example here.

JQ Expression Tool Inputs

First, most MCP tools just return JSON. Too much JSON, as we have already covered, but JSON. Second, we’ve already established that models are trained to be absolute power users of UNIX command line tools. The undisputed champion of concise transformation of JSON on the unix command line is the tool jq. Why not just have our MCP tools accept their transformation as an input. Let me show you what I mean.

Consider again an MCP that provides weather information. We might have a tool that returns the hourly forecast for the next 48 hours. The output of such a call might look something like this,

[
  {
    "time": "2026-03-11T10:00:00Z",
    "summary": "Mostly Clear",
    "icon": "clear-night",
    "temperature": 60.49,
    "apparent_temperature": 59.9,
    "wind_speed": 5.36,
    "wind_bearing": 220,
    "pressure": 1018.11,
    "humidity": 0.94,
    "precipitation_probability": 0,
    "precipitation_intensity": 0,
    "dew_point": 58.24,
    "visibility": 10,
    "cloud_cover": 0.18
  },
  // ... 47 more of these ...
]

Typically, this tool might take no input arguments or it might have a random set of knobs giving limited control of what is returned. What if, instead, it took in a jq expression that allowed the agent to transform the JSON to only what it needs. In the MCP server, the tool’s input schema might look like this,

{
  "description": "Get the hourly weather forecast for the local area for the next 48 hours",
  "inputSchema": {
    "additionalProperties": false,
    "properties": {
      "jq_expression": {
        "description": "A JQ expression to transform the hours property in the result to select only the data that is needed. The context (.) refers to the array contained in the hours property.",
        "type": "string"
      }
    },
    "type": "object"
  },
  "name": "hourly_forecast",
  "outputSchema": {
    "properties": {
      "filtered_hours": {
        "description": "The hours property filtered by the JQ expression"
      },
      "hours": {
        "description": "Hourly weather forecast for the local area for the next 48 hours",
        "items": {
          "properties": {
            "apparent_temperature": { "type": "number" },
            "cloud_cover": { "type": "number" },
            "dew_point": { "type": "number" },
            "humidity": { "type": "number" },
            "icon": { "type": "string" },
            "pressure": { "type": "number" },
            "summary": { "type": "string" },
            "temperature": { "type": "number" },
            "time": { "type": "string" },
            "visibility": { "type": "number" },
            "wind_bearing": { "type": "number" },
            "wind_speed": { "type": "number" }
          },
          "required": [ "time", "summary", "icon", "temperature", "apparent_temperature", "dew_point", "humidity", "wind_speed", "wind_bearing", "visibility", "cloud_cover", "pressure" ],
          "type": "object"
        },
        "type": [ "null", "array" ]
      }
    },
    "type": "object"
  }
}
If that wall of JSON looks unfamiliar, it's how a tool residing in an MCP server is presented to the agent. I have removed some of the details to make it more readable. See the MCP Specification for the full details.

In theory, this gives our agents a knob they can use to filter the data to only what they need, the same kind of knobs that Claude so masterfully deploys when it’s Gibberdibbling… in the terminal. For example, if the agent needs only the temperature for the next 24 hours, it can avoid having the large blob of JSON in the context window by calling the houly_forecast tool like this,

hourly_forecast({jq_expression: “[ .[0:24][] | .temperature ]”}) 

Returning a nice, concise array of temperatures.

[ 39.06, 37.89, 36.72, 37.72, 39.24, 41.4, 43.34, 44.46, 46.18, 47.32, 47.89, 48.16, 47.41, 46.72, 44.98, 43.63, 43, 42.66, 41.41, 40.46, 39.51, 38.43, 37.33, 35.51 ]

Does this work?

Yes. I think so. Honestly, I don’t have enough data to know how reliable this is, but it is passing the vibes check. And to my surprise, it’s even passing the vibes check on small open weights models. Here’s gpt-oss:20b making use of the jq expressions.

Better models, as you would expect, do even better. Not only do they tend to get the expression right on the first iteration, but they correct mistakes far faster than the smaller models. That’s not surprising. I can’t say definitively if this “works” for production agents, but it has been working well enough for me that I’m inclined to explore it further.

Other Alternatives

As I said earlier, I don't want to throw out MCP servers right now. Most AI tools can connect to them. Most companies plan to ship one. The protocol and infrastructure is far from ideal, but it currently seems to be the most practical way to give an AI agent access to most of your important data. I can configure Claude with MCP servers and there is a reasonable set of steps to authenticate with OAuth so that the MCP server knows who I am and what data I can access. Throwing all of that out in favor of command line tools would, at least in the short term, lock out most users. So even if the JQ expressions strategy that I describe above doesn’t work out, I think it’s worth continuing to try to make MCP servers work. For instance, the Anthropic article that I posted earlier, …, describes programmatic tool calling where they use a python runtime to call tools. Claude writes python that calls tools and transforms the data as needed to satisfy the goal. This is a cool idea, but it does require a full runtime. In the case of Antropic, they are using python. I have also done some prototypes that do this with Javascript. Allowing agents to run arbitrary code introduces a lot more complexity, which is why I’m trying to start with a very limited runtime, jq.

Code on Github