Lessons from building Fabi's AI Analyst Agent MCP server

TL;DR: We built a production MCP server to expose Fabi's AI analyst agent to other LLM-powered apps and workflows, expecting a smooth implementation. Instead, we encountered OAuth quirks across different clients, inconsistent streaming support, and discovered why most MCP servers are just disquised API wrappers. From handling long-running agent calls to navigating evolving specs and authentication headaches, here's what we learned about building MCP servers that actually work in production.

Fabi's MCP server: AI data analysis for your workflow

Fabi.ai is an AI-powered data analyst that connects directly to your databases and helps you generate insights, build dashboards, and automate reporting—all through natural language conversations. As a comprehensive AI BI platform, Fabi enables self-service analytics, AI data visualization, and AI reporting without requiring SQL expertise.

With Fabi's Model Context Protocol (MCP) server, you can integrate these AI data analysis capabilities directly into your development tools and AI assistants. Enable ChatGPT, Claude, Cursor, or any MCP-compatible tool to chat with your data, perform ad hoc analysis, create Python dashboards, and save Smartbooks programmatically—all without leaving your data workflow. Whether you're doing Python data analysis or need a full BI platform for data collaboration, it's the fastest way to add AI-powered data analysis to any application or interface.

When we started building our Fabi MCP server, we thought it would be a smooth ride. After all, MCP has been around for about a year, with growing adoption across agentic ecosystems like ChatGPT, Claude, Cursor, and Claude Code, not to mention workflow tools such as AgentKit and n8n.

Our goal seemed simple: Expose Fabi's analyst agent as a callable tool that other LLM-powered apps or workflows could use.

This agent doesn't just spit out answers—it reasons deeply, calls RAG, performs dry-run code execution, troubleshoots intermediate steps, and returns a well-grounded, reliable conclusion. In other words, it's an agent-to-agent collaboration layer.

For security reasons, we wanted to host and deploy the server entirely within our own network. Sounds straightforward, right?

Spoiler: it wasn't.

What followed was an unexpected journey into evolving specs, mismatched OAuth flows, and client quirks. Here's what we learned and what you should watch out for if you're heading down the same path.

MCP: The “USB-C port” for AI apps

MCP, short for Model Context Protocol, is an open standard designed to unify how LLMs and AI apps connect to external tools, data, and workflows. Think of it as a universal adapter — a “USB-C port for AI systems.” Instead of hardcoding integrations, LLMs can auto-discover and call external resources through MCP.

(Image source: modelcontextprotocol.io)

Unlike traditional APIs where endpoints are rigid and schemas are tightly defined, MCP tools are meant to be semantic, flexible, and context-friendly. They’re designed for LLM reasoning, not just machine-to-machine communication.

That distinction matters. Many existing “MCP servers” are just API wrappers. They map endpoints to tools but fail to simplify or abstract their logic for LLMs. In our experience, those wrappers are of little use.

API wrappers ≠ true MCP servers

Here’s the problem: APIs are precise and parameter-heavy. LLMs prefer minimalism and intent-driven interfaces. When you try to expose raw APIs through MCP, you end up frustrating the model — and the user. For example, we once connected to a popular product analytics MCP server and asked for the number of active users. The LLM immediately asked:

  • “What’s your project_id?”
  • “What timeframe do you mean by ‘active’?”
  • “Which events count as active?”

At that point, if I need to supply all those details, why bother with MCP at all? I might as well call the API directly. Even worse, when multiple MCP servers are connected, LLMs start struggling to pick the right tool. Too many tools, too much context confusion and the model’s performance drops sharply.

Lesson learned: Build tools that represent intent, not implementation. MCP tools should be simple, abstracted, and aligned with natural reasoning, not replicas of REST endpoints.

Streaming support

Our analyst agent isn’t a one-shot call. It reasons iteratively, performing RAG, code dry-runs, and multi-step evaluations. Some analyses take one or two minutes, so we rely on streaming responses in our web app. In theory, MCP supports this. Tool calls can include a progress token, and clients can subscribe to stream updates. In practice, though:

  • Only Cursor properly displays progress updates, as we saw.
  • Other clients just show a spinner, leaving users to wonder if the call is stuck.
  • Some even timeout mid-call if they don’t see an early response.

To make it work, we had to get creative. Our MCP server responds early and provides a polling tool for long-running calls, allowing clients to fetch intermediate status or final results asynchronously.

A word of caution: MCP is phasing out SSE (Server-Sent Events) in favor of streamable HTTP. Choose your stack accordingly or risk reworking it later.

Authorization: The OAuth odyssey

Because Fabi connects to customer data sources, our MCP server needed robust authentication and authorization. We implemented both:

  • Token-based auth for local and automated setups.
  • OAuth 2.1 for ChatGPT, Claude and other clients.

OAuth is a standard, right? Sure, until every client interprets it slightly differently. Here’s what tripped us up:

  1. Trailing Slash Chaos: We used ASGI uvicorn with Starlette, which automatically appended / to route paths. ChatGPT, as a client, handled it fine, but Claude stripped it during redirect — causing authentication failures. The annoying trailing slash = one lost afternoon.
  2. Inconsistent OAuth Protected Resource: Some clients expect /.well-known/oauth-protected-resource others /.well-known/oauth-protected-resource/mcp.
  3. The Silent 401 Trap: Early on, we didn’t return a 401 Unauthorized right away on the /mcp endpoint delaying it until after OAuth. This led some MCP clients to silently submit unauthenticated calls — making us think OAuth was broken when it wasn’t. Another “protocol gotchas” you only discover by faceplanting into it once. Always return 401  immediately when no valid token is present. Fail fast.
  4. Cursor Missing Sufficient Scope: Some MCP clients does not require sufficient scope in OAuth requests, even though our server has declared the desirable scope,  which breaks authorization unless you handle it specifically.
  5. Dynamic Client Registration Hell: MCP “suggests” dynamic client registration — but in reality, it’s mandatory.

We use Auth0, which creates a new app per registration. Unfortunately, Auth0 caps app counts, making it a terrible fit for MCP. You’ll either need an auth proxy or a different authorization server vendor entirely.

TL;DR: Don’t use Auth0 for MCP unless you enjoy debugging OAuth flows at 2 a.m.

What we wish we knew earlier

Building the Fabi MCP server was a humbling experience. We expected a straightforward implementation, but ended up in a maze of spec quirks, evolving standards, and unpredictable client behaviors. That said, MCP's vision of universal, interoperable AI tool integration is absolutely the right direction, enabling seamless data collaboration and self-service analytics across any AI BI platform or data workflow.

We're early adopters on a protocol that's still maturing, and that's part of the fun. The result is a powerful integration that brings AI data analysis, AI data visualization, and AI reporting capabilities to any MCP-compatible tool, whether you're performing ad hoc analysis, building Python dashboards, or conducting Python data analysis within your existing BI platform.

If you're building your production-ready secure MCP server:

  • Keep your tools simple and intent-based.
  • Plan for streaming and long-running jobs.
  • Expect quirks from OAuth integration.

Shoutouts

Huge thanks to Till Dohmen from MotherDuck for the valuable discussions and insights. Your shared experience helped us navigate more smoothly through this MCP labyrinth.

Related reads
Subscribe to Query & Theory