DEV Community

Cover image for My JSON Was Too Big for My AI, So I Built an MCP Server to Fix It
Gautam Vhavle
Gautam Vhavle

Posted on

My JSON Was Too Big for My AI, So I Built an MCP Server to Fix It

It was late night. My eyes were burning. I had a JSON file staring back at me. A space missions database (example). 1620+ lines. 7 missions, each nested 5 levels deep. Personnel records inside crew arrays inside missions inside a database meta object. Spacecraft specifications with Ion-Plasma Drive stats. Payloads marked as "Class-IV Radiation Hazard." Budgets in the billions.

It was the most beautifully structured chaos I'd ever seen.

I thought, "Let me just ask my AI to analyze this."

So I pasted the whole thing into the chat. Hit enter. Waited. And then, the message I'd been dreading:

⚠️ Context limit exceeded. I was using a small open-source LLM

My LLM couldn't even see the whole file. And the worst part? I didn't need all of it. I just wanted one number: the total budget across all 7 missions. One number, buried 4 layers deep inside missions[*].budget_credits. That's it.

I sat there for a second. Then I thought about all the times I'd hit this exact wall before. Huge API responses from clients, MongoDB exports with 10,000 nested documents, config mega-files that no human should ever have to scroll through. Every time, the same story: JSON too big, AI too small.

I stared at the screen. Then I did what any sane developer would do.

I built something.


Why I Couldn't Just "Write a Script"

The Frustration Moment

Now, I know what you're thinking. "Just write a Python script, bro."

And sure, I could:

import json

with open("missions.json") as f:
    data = json.load(f)

total = sum(m["budget_credits"] for m in data["missions"])
print(f"Total budget: ${total:,.2f}")
Enter fullscreen mode Exit fullscreen mode

Six lines. Done. Right?

Except... I don't always know what I'm looking for. Sometimes I'm exploring. Sometimes I get a JSON from a client with zero documentation and I just need to poke around. "What keys exist? What's nested under config? How many items have status: active?" That exploratory, conversational flow is exactly what AI assistants are supposed to be good at.

I could use jq. I could fire up pandas. I could write a new throwaway script every single time. But all of those require me to already know the structure, already know the question, and essentially do the AI's job for the AI.

That defeated the whole purpose. I didn't want to write code to understand my data. I wanted to talk to it.

So the question became: What if my AI didn't need to read the entire file? What if, instead of choking on 620 lines of JSON, it could surgically extract exactly the pieces it needed?

I figured out, we have to use a MCP solution here.


Enter MCP — Giving Your AI Hands, Not Just Eyes

The MCP Concept

If you haven't heard of MCP (Model Context Protocol) yet, here's the simplest way I can explain it:

Instead of making your AI read an entire library, you give it a librarian.

MCP lets you give your AI tools. Small, focused functions it can call on its own. The AI decides which tool to use, calls it, reads the result, and decides its next move. It's like upgrading your assistant from a speed reader (who runs out of pages) to a detective with the perfect toolkit.

Here's the paradigm shift that changed everything for me:

❌ Without MCP: Paste 620 lines of JSON → "Context limit exceeded"

✅ With MCP: AI calls load_json → gets metadata (47KB, 3 top-level keys) → calls sum_values on $.missions[*].budget_credits"$18,250,000,000.50"

The AI never saw the full file. It loaded it into memory, used a surgical tool to extract the number I needed, and gave me the answer. Four tool calls. Zero context overflow. That was my lightbulb moment.

And that's exactly what I built.


What I Built: Universal JSON Agent MCP

Talk to your JSON files using natural language.

26 tools. 9 categories. One install command. Zero config headaches.

uv tool install universal-json-agent-mcp
Enter fullscreen mode Exit fullscreen mode

That's it. You now have an MCP server that works with VS Code / GitHub Copilot, Claude Desktop, Cursor, or literally any MCP-compatible client.

But numbers don't tell the story. Let me show you what it actually feels like to use this. Remember that space missions database I was struggling with?

You:   Load missions.json and tell me what's inside.

AI:    ✅ Loaded "missions" — object with 3 top-level keys,
       file size 47.2 KB.
       Keys: database_meta, missions, global_notes

You:   What's the structure look like?

AI:    database_meta (object)
         ├─ version (string)
         ├─ total_missions (number)
         ├─ contact (object)
         │    ├─ director (string)
         │    └─ hotline (string)
       missions (array of 7 objects)
         ├─ codename (string)
         ├─ status (string)
         ├─ budget_credits (number)
         ├─ spacecraft (object) → designation, specifications…
         └─ personnel (object) → commander, crew[]…
       (Used: get_structure)

You:   How many missions are there?

AI:    7 missions total.
       (Used: count)

You:   What's the total budget across all missions?

AI:    The total budget across all missions is $18,250,000,000.50
       (Used: sum_values on $.missions[*].budget_credits)

You:   Show me only the in-progress ones, sorted by priority.

AI:    Found 3 missions with status "in_progress":
       1. Stellar Vanguard  — priority 1, budget $4.5B
       2. Deep Core Echo    — priority 2, budget $3.2B
       3. Nebula Drift      — priority 4, budget $1.8B
       (Used: filter_objects → sort_by → pick_fields)

You:   Export that to CSV.

AI:    ✅ Exported 3 missions to in_progress_missions.csv
       (Used: export_csv)
Enter fullscreen mode Exit fullscreen mode

Six natural language questions. Zero scripts. The AI figured out which of the 26 tools to chain together (load, explore, count, aggregate, filter, sort, pick fields, and export) all by itself.

That's the magic. I didn't build one smart tool. I built 26 small, dumb tools and let the AI be the smart one.

Query Screenshot


The Toolkit — A Quick Tour

I'm not going to bore you with a feature table (that's what the README is for). Instead, let me show you when you'd reach for each of these:

🔓 Load it — You've got a JSON file. Maybe it's on your desktop, maybe it's in a project folder. You tell the AI to load it. Boom, it's in memory with full metadata: file size, root type, how many top-level keys. You can load multiple files and switch between them mid-conversation.

🔍 Explore it — You just received a 50MB JSON dump from a client. No documentation. No schema. You don't even know what keys exist. This is where get_structure becomes your flashlight in the dark. It shows you a clean skeleton of the entire file: keys, types, nesting depth. All without loading a single data value into your chat. distinct_paths goes even further and maps out every single leaf path in the document, even inside nested arrays. Think of it like turning on all the lights in a building you've never been in.

🎯 Query it — Now you know what's inside. Time to ask questions. Full JSONPath support ($.missions[*].codename), regex-powered text search across all string values, and smart filtering with 8 operators (equals, greater than, contains, regex match, and more). You can say "find all personnel whose role contains 'engineer'" and get precise results, without ever reading the parts of the file you don't care about.

📊 Crunch it — This is where it gets really fun. Sum all budgets. Count active missions. Get min/max priority levels. Run a full statistical breakdown (mean, median, standard deviation, percentiles) on any numeric field. Even get a frequency table, like value_counts() in pandas, but through a conversation. All the number-crunching happens server-side; your context window stays clean.

🔄 Transform it — Flatten nested objects into dot-notation key-value pairs. Pick only the fields you care about. Group missions by status. Sort by priority, descending. Reshape the data into exactly the view you need before asking the next question.

📤 Export it — Happy with the filtered, sorted, transformed result? Export to CSV or JSON with one sentence. Done. Ship it.

Here's the design philosophy I kept coming back to: each tool does one small thing. The AI chains them together to build complex analyses. Kind of like UNIX pipes, but instead of |, it's intelligence connecting the tools.

That idea (small tools, composed by an AI) turned out to be way more powerful than any "one mega-tool that does everything" approach I could have built.


Under the Hood: The Design Decisions I'm Proud Of

I'll keep this section focused. This is a story, not a textbook. But there are a few engineering choices that made this whole thing work, and I think they're worth sharing.

Smart Truncation — The Core Innovation

This was the entire point of the project, so let me explain why it matters.

When you ask get_value on the root of a 620-line JSON, you could get 620 lines dumped into the AI's context. That's exactly the problem we're solving, right? So instead, every single tool response is automatically capped at ~10KB. If the result is too large, it gets cleanly truncated:

"Array with 347 items — showing first 50. Use filters or paths to narrow results."

The AI reads that message, understands it needs to be more specific, and adjusts its next query. It never gets overwhelmed, no matter how enormous the underlying data is. This one design decision, aggressive truncation with helpful hints, is what makes it possible to work with JSON files of any size through a context-limited LLM.

The Store Pattern

When you load a JSON file, it goes into an in-memory store, not into the AI's chat. The AI gets back metadata (file size, root type, number of keys, estimated memory usage), and from that point on, every tool works against the stored data. Load once, query a hundred times. You can even load multiple files and run comparisons across them.

Fail-Safe Tool Responses

LLMs use tools in loops. They call a tool, read the result, decide the next step. If a tool throws an unhandled exception, the entire reasoning loop can break. So every tool in this project catches errors gracefully and returns a readable error message instead of crashing:

def _safe(fn, *args, **kwargs) -> str:
    try:
        return fn(store, *args, **kwargs)
    except Exception as exc:
        return f"Error: {exc}"
Enter fullscreen mode Exit fullscreen mode

The AI sees "Error: Key 'budgt' not found. Available keys: budget_credits, budget_cycle", adapts, corrects the typo, and continues. No crash. No broken loop. The conversation just keeps going.

The UNIX Philosophy, Applied to AI

This is the architectural insight I keep coming back to. I didn't try to build one omniscient JSON-processing mega-tool. Instead, I built 26 tiny tools that each do exactly one thing. The AI figures out how to chain them together.

load_json loads. get_structure explores. filter_objects filters. sum_values sums. export_csv exports. That's it. Each one is stupid simple. But when an AI strings together load → structure → filter → sort → pick_fields → export? That's when the magic happens.

The composability is the whole point. And it makes the codebase dead simple to extend. Adding a new tool is about 50 lines of code.


My First PyPI Package — The Terrifying, Beautiful, Amazing Part 🎉

PyPI Launch Celebration

Okay, can we just... pause for a second?

This was my very first Python package. Ever. Published. On PyPI. Where actual developers go to install actual packages. And now mine is sitting there next to requests and flask and numpy (okay, maybe not next to them, but on the same website and that counts).

There's something weirdly emotional about naming a package. You're not just picking a variable name that lives in one file. You're picking a name that will live on the internet. Forever. People will type this name. Into their terminals. With their own fingers.

I went back and forth. Too generic? Someone already took it. Too clever? No one will find it. I finally landed on universal-json-agent-mcp and thought, "Yeah. That's the one. That says exactly what it does."

And then I hit publish.

I remember staring at the PyPI page after it went live, just... refreshing it. Like a kid checking if their YouTube video got views yet. The badges were there. The description was there. My name was there. My name. On PyPI. On the same platform where requests and numpy live.

Then I opened a fresh terminal on a completely clean machine. Typed:

pip install universal-json-agent-mcp
Enter fullscreen mode Exit fullscreen mode

Watched the progress bar. It downloaded. It installed. I ran it. It worked.

I'm not going to pretend I was cool about it. I wasn't. If you've been sitting on a project, thinking "it's not ready" or "no one will use it"... just ship it. Seriously. The first version doesn't have to be perfect. It just has to exist.


Works Everywhere — Zero Lock-In

One of my non-negotiables from day one was universal compatibility. I didn't want to build a VS Code-only tool or a Claude-only plugin. If your AI editor speaks MCP, this should just work.

Here's the entire setup for VS Code, and it's similarly simple for Claude and Cursor:

// .vscode/mcp.json
{
  "servers": {
    "universal-json-agent": {
      "type": "stdio",
      "command": "universal-json-agent-mcp"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

That's it. Eight lines. Restart your editor, and every JSON file on your machine is now queryable through natural language. No API keys needed for the MCP server itself, no cloud dependency, no vendor lock-in. It runs locally, on your machine.


Bonus Chapter: The Web Server

But what about when you're not in an editor?

What if you want to query JSON from a dashboard, a CI pipeline, or a custom internal tool? That's where the bonus FastAPI + LangChain web server comes in.

It's an optional subproject bundled in the same repo. Upload a JSON file, ask a question in plain English, get an answer back, along with which tools were used. Under the hood, it runs a full LangChain ReAct agent. The same reasoning loop that Copilot and Claude use, but wrapped in a REST API:

curl -X POST http://localhost:8000/query \
  -F "file=@data/missions.json" \
  -F "query=What's the total budget?"
Enter fullscreen mode Exit fullscreen mode
{
  "answer": "The total budget across all missions is $18,250,000,000.50",
  "tools_used": ["load_json", "sum_values"]
}
Enter fullscreen mode Exit fullscreen mode

The agent's system prompt is specifically tuned to use bulk extraction patterns. It knows to use JSONPath wildcards like $.missions[*].budget_credits instead of iterating one-by-one. Same 26 tools, same core logic, zero code duplication. Just a different interface.

Perfect for building quick dashboards or integrating JSON analysis into automated workflows.


Who Is This For?

Honestly? If you've ever stared at a JSON file and thought "this is too much"? It's for you.

But if you want specifics:

  • Backend developers debugging massive API responses at 11 PM, trying to figure out why that one nested field isn't what the docs say it should be
  • Data engineers who just got a JSON export with zero documentation and need to figure out its structure before writing a pipeline
  • Anyone who's ever pasted a JSON into ChatGPT, Claude, or Copilot and hit the context limit? Never again
  • Students and learners who want to explore datasets conversationally without writing boilerplate ETL code
  • Teams building internal tools who need a quick JSON analysis layer without spinning up a whole data stack

If you work with JSON (and let's be honest, who doesn't in 2026), this tool has a place in your workflow.


Final Thoughts

That late night frustration didn't just produce a tool. It produced my first PyPI package.

I built this because I needed it. No one asked me to. I had a problem. My JSON was too big for my AI, and I refused to accept that as a limitation. What started as a late-night hack turned into something with proper architecture, hundreds of tests, a published package, and support for every major AI editor.

If there's one thing I've learned, it's that the best tools come from scratching your own itch. And now this one's out there for anyone who's ever stared at a monster JSON and thought, "There has to be a better way."

Your JSON is too big? Not anymore.


⭐ If this resonated with you, give the repo a star.

GitHub · PyPI · Report Issues

Top comments (0)