Skip to content
SferaDev
Back to blog

From OpenAPI spec to MCP: How we built Xata's MCP server

A deep dive into building an OpenAPI-driven Model Context Protocol (MCP) server using Kubb for code generation, custom TypeScript generators, and Vercel's Next.js MCP adapter to enable seamless AI integration.

XataOpenAPIMCP

Learn how we built an OpenAPI-driven MCP server using Kubb, custom code generators, and Vercel’s Next.js MCP adapter.

What is MCP?

Model Context Protocol (MCP) is an emerging standard that lets AI models securely interact with tools and APIs in real time. Building an MCP server means exposing a set of “tools” (operations) that a Large Language Model (LLM) can call to perform tasks, like fetching data or triggering actions via your backend. We generated our MCP server from our existing OpenAPI spec, making it the single source of truth—fast, consistent, and reliable.


🚧 Key Challenges

  • One-to-one endpoint mapping is overwhelming. LLMs struggle choosing the right tool among many low-level options.
  • Hand-coding is time consuming and error-prone. Manually writing tool schemas and handlers is unsustainable for large APIs.

🛠️ Our Hybrid Approach

  1. Auto-generate base tools from the OpenAPI spec.
  2. Curate and enhance them manually to improve clarity and avoid noise.
  3. Leverage OpenAPI as the source of truth—no duplicated maintenance.

We use codegen to generate tool definitions and client calls, then prune or revise them based on real needs.


1. Migrating from OpenAPI Codegen to Kubb

We replaced our previous rigid generator with Kubb, a flexible toolkit for generating types, API clients, Zod validators, React hooks, and MCP code directly from OpenAPI.

Why Kubb

  • Plugin architecture makes output formats customizable.
  • You can plug in custom client generators or MCP tool generators.
  • Single-pass parsing: parse once, generate many outputs.

Sample kubb.config.ts

import { defineConfig } from "kubb";
export default defineConfig({
  input: ["path/to/openapi.json"],
  plugins: ["@kubb/plugin-oas", "@kubb/plugin-ts"],
  generators: [
    "clientGenerator", // custom
    "mcpToolGenerator" // custom
  ],
});

Run with:

pnpm kubb generate

2. Custom Generators: API Client & MCP Tools

a. Typed API Client

  • Generates TypeScript functions per operation (from operationId).
  • Uses fetch, built-in auth, and developer ergonomics.
  • Keeps client in sync with the API spec—no outdated or buggy code.

b. MCP Tool Definitions

Using mcpGenerator:

  • Produces initMcpTools(server) initializing each tool.
  • Curates endpoint exposure; prunes internal ones.
  • Converts developer-facing descriptions into AI-friendly ones.
  • Uses Zod schemas for input validation—bad commands fail before hitting API.

3. Building the MCP Server with Next.js

We integrated with Vercel using @vercel/mcp-adapter in a Next.js API route:

import { createMcpHandler } from "@vercel/mcp-adapter";
import { initMcpTools } from "./generated-mcp-tools";
import { withAuth } from "./auth";

export const GET = withAuth(
  createMcpHandler(async (server) => {
    initMcpTools(server);
  }, { redisUrl: process.env.REDIS_URL })
);
export const POST = GET;

👉 Handles both SSE and HTTP, auth middleware, and persistent conversation state via Redis.


🧩 MCP vs Traditional APIs

Instead of directly calling REST endpoints, the LLM:

  1. Handshakes to discover available tools.
  2. Picks a named tool, e.g. list_databases, with args.
  3. The server runs the tool, fetches data via the API client, and returns results.

It functions like an RPC interface—capabilities-based and dynamic.


✅ Conclusion

By treating the OpenAPI spec as executable knowledge:

  • We rapidly generated MCP tools and clients with minimal manual work.
  • We enforced strong type safety and validation (via Zod).
  • We enabled a powerful conversational interface backed by real APIs.

🌟 The result: a robust, AI-ready backend—quick to build, easy to maintain.


If you're interested in AI-backend integration, check out Xata’s serverless Postgres (“Postgres at scale” with branching, PII anonymization) and our MCP server example. Happy coding!