Modern web apps are no longer just UI-driven—they’re AI-assisted, context-aware, and action-oriented. With Model Context Protocol (MCP), you can connect AI models directly to real-world services like Zomato in a structured and secure way.
In this blog, we’ll walk through how to integrate Zomato MCP in a Next.js app, using best practices for scalability, security, and performance.
Why Use MCP with Next.js?
Next.js is a great fit for MCP-based AI apps because it provides:
- API Routes / Route Handlers for MCP servers
- Server Components for secure AI + API calls
- Edge & Server runtimes for performance
- Easy integration with AI SDKs
By combining Next.js + MCP + Zomato, you can build:
- AI-powered food discovery apps
- Smart travel & food planners
- Conversational restaurant recommendation tools
High-Level Architecture (Next.js + MCP)
Here’s how everything fits together:
- Next.js App (UI)
- AI Layer (LLM / Agent)
- MCP Server (Next.js API route)
- Zomato API
Flow:
- User asks a question in the UI
- LLM decides to use a Zomato MCP tool
- Next.js MCP route calls Zomato API
- Structured data flows back to the AI
- AI responds with contextual results
Step 1: Create an MCP Server Using Next.js API Routes
In a Next.js (App Router) project, your MCP server can live inside:
app/api/mcp/zomato/route.ts
This route will:
- Expose Zomato capabilities as MCP tools
- Validate inputs
- Call Zomato APIs securely
- Return structured JSON responses
Why this works well:
- API keys stay on the server
- Easy deployment on Vercel or Node runtimes
- No separate backend required
Step 2: Define Zomato MCP Tools
Each Zomato capability is exposed as an MCP tool, for example:
- Search restaurants
- Filter by cuisine, rating, budget
- Fetch restaurant details
Each tool includes:
- Tool name
- Clear description (for the AI)
- Input schema
- Output schema
This helps the AI decide when to call Zomato automatically, instead of relying on brittle prompt logic.
Step 3: Securely Call Zomato APIs
Inside your Next.js MCP route:
- Store Zomato API keys in
process.env - Normalize Zomato responses
- Remove unnecessary fields
- Handle errors and rate limits
Best practices:
- Cache frequent queries (location + cuisine)
- Add request timeouts
- Log tool usage for observability
Step 4: Connect MCP to the AI Layer
Once your MCP endpoint is live:
- Register it as a tool with your LLM
- Enable tool/function calling
- Let the AI choose when to invoke Zomato MCP
Now your Next.js app can handle queries like:
“Find the best South Indian food near me under ₹250”
The AI:
- Understands intent
- Calls the Zomato MCP tool
- Combines results with reasoning
- Returns a clean, user-friendly answer
Step 5: Build the Next.js UI
On the frontend, you can:
- Use Server Actions for AI requests
- Stream responses for better UX
- Show restaurant cards, maps, or filters
Common UI patterns:
- Chat-style food assistant
- Location-based discovery
- AI-powered search bar
- Recommendation panels
Because MCP runs server-side, your UI stays:
- Fast
- Secure
- SEO-friendly
Example Use Cases in Next.js
🍽 AI Restaurant Finder
“Show me top-rated cafes near Connaught Place open now”
🧠 Smart Meal Suggestions
“What should I eat if I want something healthy and spicy?”
✈️ Travel + Food Planner
“Plan lunch and dinner near my hotel for 2 days”
🧪 Personalization
Food recommendations based on past searches or preferences
Best Practices for Next.js + MCP
- Use Server Components for AI calls
- Keep MCP schemas simple and descriptive
- Add fallback responses when APIs fail
- Cache aggressively at the MCP layer
- Start with read-only actions before expanding
Common Challenges
- Zomato API rate limits
- Location accuracy
- Ambiguous user queries
- Cost control for AI + API usage
Solving these at the MCP level keeps your Next.js app clean and maintainable.
This setup allows you to build production-ready AI apps that can reason, act, and scale—all within a single Next.js codebase.
Below is a complete, end-to-end Next.js (App Router) implementation showing how to integrate Zomato via MCP (Model Context Protocol) in a real, runnable way.
This includes:
- MCP server (API route)
- Zomato API integration
- Tool definitions
- AI tool calling
- Frontend UI (chat-style)
- Secure environment setup
I’m assuming:
- Next.js 14+ (App Router)
- Node runtime
- OpenAI / compatible LLM with tool calling
- Zomato API access
1️⃣ Project Structure
/app
├─ /api
│ ├─ /mcp
│ │ └─ /zomato
│ │ └─ route.ts # MCP Server
│ └─ /chat
│ └─ route.ts # AI Orchestrator
├─ page.tsx # UI
└─ layout.tsx
/lib
├─ zomato.ts # Zomato API wrapper
├─ mcp-tools.ts # MCP tool definitions
└─ openai.ts # AI client
.env.local
2️⃣ Environment Variables (.env.local)
OPENAI_API_KEY=sk-xxxx
ZOMATO_API_KEY=your_zomato_api_key
3️⃣ Zomato API Wrapper (lib/zomato.ts)
export async function searchRestaurants({
city,
cuisine,
budget,
}: {
city: string;
cuisine?: string;
budget?: number;
}) {
const res = await fetch(
`https://developers.zomato.com/api/v2.1/search?q=${city}&cuisines=${cuisine || ""}`,
{
headers: {
"user-key": process.env.ZOMATO_API_KEY!,
},
}
);
if (!res.ok) {
throw new Error("Zomato API error");
}
const data = await res.json();
return data.restaurants.slice(0, 5).map((r: any) => ({
name: r.restaurant.name,
rating: r.restaurant.user_rating.aggregate_rating,
address: r.restaurant.location.address,
cuisines: r.restaurant.cuisines,
costForTwo: r.restaurant.average_cost_for_two,
}));
}
4️⃣ MCP Tool Definition (lib/mcp-tools.ts)
export const zomatoTools = [
{
type: "function",
function: {
name: "search_restaurants",
description:
"Find restaurants using Zomato based on city, cuisine, and budget",
parameters: {
type: "object",
properties: {
city: { type: "string" },
cuisine: { type: "string" },
budget: { type: "number" },
},
required: ["city"],
},
},
},
];
5️⃣ MCP Server (Next.js API Route)
📍 app/api/mcp/zomato/route.ts
import { NextResponse } from "next/server";
import { searchRestaurants } from "@/lib/zomato";
export async function POST(req: Request) {
const body = await req.json();
const { city, cuisine, budget } = body;
try {
const data = await searchRestaurants({ city, cuisine, budget });
return NextResponse.json({
success: true,
data,
});
} catch (error) {
return NextResponse.json(
{ success: false, error: "Failed to fetch restaurants" },
{ status: 500 }
);
}
}
✔ This is your MCP Tool Execution Layer
6️⃣ OpenAI Client (lib/openai.ts)
import OpenAI from "openai";
export const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY!,
});
7️⃣ AI Orchestrator (Tool Calling)
📍 app/api/chat/route.ts
import { NextResponse } from "next/server";
import { openai } from "@/lib/openai";
import { zomatoTools } from "@/lib/mcp-tools";
export async function POST(req: Request) {
const { message } = await req.json();
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: message }],
tools: zomatoTools,
tool_choice: "auto",
});
const msg = completion.choices[0].message;
// If AI decides to call Zomato
if (msg.tool_calls) {
const toolCall = msg.tool_calls[0];
const args = JSON.parse(toolCall.function.arguments);
const zomatoRes = await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/mcp/zomato`,
{
method: "POST",
body: JSON.stringify(args),
}
);
const data = await zomatoRes.json();
return NextResponse.json({
role: "assistant",
content: `Here are some great options:\n${data.data
.map(
(r: any) =>
`• ${r.name} (${r.rating}⭐) – ${r.cuisines}, ₹${r.costForTwo}`
)
.join("\n")}`,
});
}
return NextResponse.json(msg);
}
✔ This is pure MCP-style tool orchestration
8️⃣ Frontend UI (app/page.tsx)
"use client";
import { useState } from "react";
export default function Home() {
const [input, setInput] = useState("");
const [messages, setMessages] = useState<string[]>([]);
async function sendMessage() {
const res = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ message: input }),
});
const data = await res.json();
setMessages((prev) => [...prev, input, data.content]);
setInput("");
}
return (
<main className="p-6 max-w-xl mx-auto">
<h1 className="text-2xl font-bold mb-4">
🍽 AI Food Assistant (Zomato MCP)
</h1>
<div className="border p-4 rounded mb-4 min-h-[200px]">
{messages.map((m, i) => (
<p key={i} className="mb-2">
{m}
</p>
))}
</div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Find biryani near Delhi under 300"
className="border p-2 w-full mb-2"
/>
<button
onClick={sendMessage}
className="bg-black text-white px-4 py-2 rounded"
>
Ask
</button>
</main>
);
}
9️⃣ Example Prompts That Work
✅ “Find the best biryani in Delhi under 300”
✅ “Suggest Italian food near Bangalore”
✅ “Good cafes in Mumbai with rating above 4”
🔐 Security & Production Notes
- Keep Zomato API keys server-only
- Add caching in MCP route
- Add rate limits
- Log tool usage
- Add fallbacks when APIs fail
🚀 Final Result
You now have:
✔ MCP-compliant tool architecture
✔ AI-driven decision making
✔ Next.js-native backend
✔ Clean UI
✔ Production-ready pattern
create mock-server for the Zomato MCP. We can directly config the mcp_config.json.
- Open Agent, click on Triple dots, and select MCP Server.
- To add custom MCP, we need to click on View raw config.
- then edit mcp_config.json.




Top comments (0)