Ram Yadlapalli

How We Built an MCP Server on Our Existing APIs

Ram Yadlapalli · VP of Product Development and Engineering · Dallas · November 2026 · 14 min read

We built an MCP server for our product in a few weeks. Most of it was reuse: the same reporting APIs, the same identity provider, the same Kubernetes cluster and pipeline. This is a write-up of what we did, what worked, and what we plan next.

Contents

Why

Our customers have a lot of operational data in our product: orders, events, exceptions, team and location performance. Today they see it through report screens. When they have a question the screens don't answer, it becomes a feature request or an Excel export.

Most of our customers already use an AI assistant. So the ask was: let them connect their own assistant to their data in our product, with the same permissions they have in the web app. No new copy of the data, no new login.

flowchart LR
    subgraph Today
        Q1[New question] --> F1[Feature request] --> F2[Report built<br/>weeks later] --> F3[Answer]
        Q1 --> E1[Export to<br/>Excel] --> F3
    end
    subgraph With MCP
        Q2[Question in<br/>plain language] --> AI[AI assistant] --> T[MCP tools] --> A2[Answer and chart]
    end

What MCP is

MCP (Model Context Protocol) is an open standard for connecting AI assistants to other systems. A server exposes tools. Each tool has a name, typed inputs and a description. The assistant reads the descriptions, decides which tools to call and uses the results in its answer. Since it is a standard, one server works with Claude, other assistants, IDEs and the customer's own agents.

flowchart LR
    C1[Claude] --> S
    C2[Other AI assistants] --> S
    C3[IDE and agent tools] --> S
    C4[Customer's own agent] --> S
    S[MCP server] --> P[(Operational data)]

We looked at two other options before choosing MCP:

Option Why not
Build our own chatbot We would own the model, the UI and the cost. Customers already have assistants.
Export data to a warehouse for AI New pipelines, stale data, a second permission model and a bigger security review.
MCP server Chosen. Works with any assistant and uses our existing security.

Where the tools get data

There were three choices:

  1. A new data API built for AI.
  2. A warehouse or lakehouse loaded with reporting data.
  3. The reporting APIs our web app already uses.

We went with the third. The reporting service already finds the tenant for each request and connects to that tenant's database, checks the user's permissions on every endpoint and reads through stored procedures that are tuned and tested.

flowchart TB
    Q{Where should the<br/>tools get data?}
    Q --> N[New data API]
    Q --> W[Warehouse or lakehouse]
    Q --> E[Existing reporting APIs]
    N --> N1[Duplicate tenancy,<br/>auth and queries]
    W --> W1[New pipelines,<br/>new permission model]
    E --> E1[Tenancy, permissions and<br/>queries already exist]
    E1 --> OK[Chosen for version 1]
    W1 -. "later, for analytics" .-> LT[See: Next phase]

So the MCP server only translates. A tool call comes in, it calls an existing endpoint and returns a compact JSON result. It has no database credentials. If a user can't see something in the web app, the assistant can't see it either.

Architecture

flowchart LR
    U[User] --> A[AI assistant<br/>any MCP client]
    A -- "MCP over HTTPS<br/>+ user's access token" --> G[Ingress /<br/>API gateway]
    G --> M[MCP server<br/>stateless container]
    M -- "same token<br/>+ tenant header" --> R[Existing reporting API]
    M -- "same token" --> O[Existing app API<br/>e.g. location list]
    R --> D[(Tenant reporting<br/>database)]
    A -. "OAuth sign in" .-> I[Existing identity<br/>provider]
    M -. "verify token<br/>signature" .-> I

Authentication

We expected this to be the hardest part. It was mostly configuration, because MCP uses standard OAuth.

sequenceDiagram
    participant C as AI assistant
    participant M as MCP server
    participant I as Identity provider
    participant R as Reporting API

    C->>M: Call /mcp (no token)
    M-->>C: 401 + pointer to resource metadata
    C->>M: GET /.well-known/oauth-protected-resource
    M-->>C: "My authorization server is <issuer>"
    C->>I: Discover endpoints, start sign in (PKCE)
    I-->>C: User signs in as usual, gets access token
    C->>M: Call tool with Bearer token
    M->>M: Verify signature and issuer
    M->>R: Same request, same token
    R-->>M: Data the user is allowed to see
    M-->>C: Compact result

Public client instead of dynamic registration

MCP clients can register themselves with the identity provider automatically (dynamic client registration). Many enterprise identity setups don't allow that. Our first try failed with a generic "couldn't register" error. We created one public client for MCP, allowed only the assistant's redirect URL, and entered that client ID in the connector settings. That fixed it.

Token claims

Our APIs filter data using claims in the token, for example which locations a user can see and which roles they have. A new client doesn't get the same claims as the web app client by default, so we added the same claim mappers. Check this early. A valid token with a missing claim returns empty results, and it takes time to figure out why.

Multi tenancy

Each customer has its own subdomain and its own identity realm. The MCP server gets everything from the host name of the request:

flowchart LR
    H["Request host<br/>customer-a.example.com"] --> P[MCP server parses host]
    P --> T1[Tenant id<br/>customer-a]
    P --> T2[Expected issuer<br/>customer-a realm]
    P --> T3[Resource metadata URL]
    T1 --> API[Tenant header on<br/>every API call]
    API --> DB[(customer-a<br/>reporting database)]
    T2 --> V{Token issuer<br/>matches?}
    V -- no --> X[401 rejected]
    V -- yes --> API

One deployment serves all tenants on a cluster, with no per tenant configuration. Signing keys are fetched from each issuer and cached.

Tool design

Most of the answer quality depends on this.

Example of one question turning into two tool calls. The model decides the order from the descriptions:

sequenceDiagram
    participant U as User
    participant A as AI assistant
    participant M as MCP server
    U->>A: "Where are the biggest delays at our Chicago site this month?"
    A->>M: list_locations(search: "Chicago")
    M-->>A: Chicago = location code CHI
    A->>M: get_slowest_events(location: CHI, from, to)
    M-->>A: Top 10 items with average delay minutes
    A-->>U: Bar chart + explanation + suggested actions
flowchart LR
    R[Raw API response<br/>every record, every event<br/>~700 KB] --> S[MCP server<br/>aggregate, rank, top N] --> O[Tool result<br/>per team totals<br/>under 50 KB]

Performance

Two of our questions ("longest delays last month" and "exceptions by location") took several minutes in the first version. The queries were parsing large JSON event records across millions of rows on every call.

We fixed it with regular database work:

flowchart LR
    subgraph Before["Before: minutes"]
        Q1[Tool call] --> J[Parse JSON events<br/>across millions of rows] --> A1[Answer]
    end
    subgraph After["After: under a second"]
        S[Scheduled refresh<br/>stored procedure] --> T[(Event<br/>summary table)]
        Q2[Tool call] --> P[Small query<br/>stored procedure] --> T
        P --> A2[Answer]
    end

Both now return in under a second.

In our tests, the MCP server and APIs take about 0.1 to 2.5 seconds per tool call. Most of the remaining time is the model thinking and writing. A faster model, lower reasoning effort, simple charts and small results help more than tuning the server further.

Hosting

Nothing new was set up.

flowchart TB
    subgraph CI["CI/CD pipeline (existing)"]
        B[Build image] --> SC[Security scan] --> PU[Push to registry]
    end
    PU --> HL[Helm release<br/>new subchart added]
    subgraph K["Kubernetes cluster (existing)"]
        IN[Ingress gateway<br/>+ /mcp and well known routes] --> MS[MCP server pods<br/>stateless]
        MS --> RS[Reporting API service]
        MS --> AS[App API service]
        IN --> WEB[Web app]
    end
    HL --> MS
    Internet[AI assistant] --> IN

Few things we ran into:

Connecting Claude

This took a few minutes:

  1. An org admin adds a custom connector with the MCP URL.
  2. In advanced settings, the admin sets our public client ID (no secret).
  3. Each user clicks Connect and signs in on the normal login page.
flowchart LR
    A1[Admin adds custom<br/>connector with MCP URL] --> A2[Admin sets public<br/>client id, no secret]
    A2 --> U1[User clicks Connect]
    U1 --> U2[Normal login page]
    U2 --> U3[Tools available in chat]

After that, a user can ask "How did the Chicago site do last Monday? Show it as a dashboard" and get the answer with charts.

What we learned

  1. The existing APIs already handled tenancy and permissions. That is why the MCP server is only a few hundred lines.
  2. Standard OAuth was enough. Resource metadata, PKCE and a public client worked with our identity provider without custom auth code.
  3. Tool descriptions, result size and defaults matter more than the number of tools.
  4. If a question is slow in SQL, it is slower through an assistant. Precompute it.
  5. Treat a live demo like a release: sign in beforehand, two replicas, and questions that return in seconds.

Next phase: Microsoft Fabric for analytics

Version 1 is good for questions about the current operation ("how is today going?"). It is not a good fit for deeper analysis:

The plan is to move analytics to a lakehouse on Microsoft Fabric. The MCP server stays the entry point for assistants.

Target architecture

flowchart LR
    subgraph Ops["Operational databases (per tenant)"]
        P[(Database A)]
        D[(Database B)]
        R[(Reporting)]
        T[(Database C)]
    end
    subgraph Fabric["Microsoft Fabric (OneLake)"]
        BR[Bronze<br/>raw mirrored tables]
        SI[Silver<br/>cleaned, conformed,<br/>JSON flattened]
        GO[Gold<br/>business metrics<br/>and KPIs]
        SM[Semantic model<br/>shared definitions<br/>+ row level security]
        BR --> SI --> GO --> SM
    end
    P & D & R & T -- "mirroring / CDC" --> BR
    X[Other sources<br/>IoT, finance,<br/>service tickets] --> BR
    M[MCP server] -- "today's operation" --> API[Existing APIs]
    M -- "history, trends, cross location" --> SM
    A[AI assistants] --> M
    BI[Dashboards and BI] --> SM

How it works

Phases

timeline
    title Roadmap
    Phase 1 (done) : MCP server over existing APIs : Read only tools : OAuth with existing identity provider : Summary tables for slow questions
    Phase 2 : Mirror operational databases into the lakehouse : Bronze and silver : First gold tables for core KPIs
    Phase 3 : Semantic model with shared metric definitions : Row level security per tenant and user : MCP tools for history and trends
    Phase 4 : More sources such as IoT and finance : Broader query tools : Write actions with human approval

For customers nothing changes. Same assistant, same login, same permissions.

Other next steps

If you already have secure APIs and an identity provider, you don't need a new platform to get started. A thin MCP server on top of them is enough for version 1. See what people ask, then decide what to build behind it.