{"id":39089,"date":"2025-11-12T07:00:00","date_gmt":"2025-11-12T15:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=39089"},"modified":"2025-12-12T15:33:12","modified_gmt":"2025-12-12T23:33:12","slug":"ai-powered-content-engine-cloudinarys-mcp-server-next-js","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js","title":{"rendered":"Building an AI-Powered Content Engine With Cloudinary&#8217;s MCP Server and Next.js"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>The path from creative idea to production-ready visual can be a long one, often filled with manual, tedious work. Translating a vision, like \u201ca dramatic, widescreen shot of a wolf overlayed with a black and white filter\u201d, into a precise string of technical parameters requires expertise and slows down content creation. What if we could automate this entire process, turning simple text prompts directly into fully transformed assets?<\/p>\n<p>In this guide, you\u2019ll build that exact solution: an intelligent visual media pipeline. You\u2019ll use a <strong>Next.js<\/strong> application to capture a user\u2019s natural language prompt. Then, we\u2019ll leverage the <strong>OpenAI API<\/strong> to interpret that prompt and dynamically generate a valid transformation command. This command will be sent to a locally running the <a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_llm_mcp\"><strong>Cloudinary MCP (Model Context Protocol) Server<\/strong><\/a>, which executes the transformation on an image sourced from the <a href=\"https:\/\/www.pexels.com\/api\/\"><strong>Pexels API<\/strong><\/a>.<\/p>\n<p>By the end, you\u2019ll have a fully functional \u201cprompt-to-production\u201d engine that automates the entire creative workflow, from sourcing to final transformation.<\/p>\n<ul>\n<li>\n<strong>GitHub Repo:<\/strong> <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\">github.com\/musebe\/ai-content-engine<\/a>\n<\/li>\n<\/ul>\n<h2>A Note on Deployment<\/h2>\n<p>The local development setup for this project requires running two separate servers in parallel: the Next.js web application and the Cloudinary MCP gateway server. To deploy a live version, this architecture must be replicated in the cloud.<\/p>\n<p>A standard Vercel deployment will only run the Next.js application. To make the project fully functional online, you\u2019ll need to:<\/p>\n<ol>\n<li>Deploy the script found at <code>scripts\/start-mcp-asset.ts<\/code> on a service designed for long-running processes, such as <strong>Render<\/strong> or <strong>Railway<\/strong> in order to give your gateway a public URL.<\/li>\n<li>Add an environment variable for <code>MCP_GATEWAY_URL<\/code> that points to your live gateway\u2019s public URL, replacing the <code>http:\/\/localhost:8787<\/code> default.<\/li>\n<\/ol>\n<h2>Prerequisites<\/h2>\n<p>Before you begin, make sure you have:<\/p>\n<ul>\n<li>\n<strong>Node.js<\/strong> (v18 or later) installed on your machine.<\/li>\n<li>A free <a href=\"https:\/\/cloudinary.com\/\"><strong>Cloudinary<\/strong><\/a> account to manage your digital media and to retrieve your API keys.<\/li>\n<li>A <a href=\"https:\/\/www.pexels.com\/api\/\"><strong>Pexels<\/strong><\/a> account to get an API key for sourcing images.<\/li>\n<li>An <a href=\"https:\/\/platform.openai.com\/\"><strong>OpenAI<\/strong><\/a> account with an API key for accessing the GPT-4 model.<\/li>\n<\/ul>\n<h2>Setting Up the Next.js Foundation<\/h2>\n<p>First, you\u2019ll need a solid base for our application. Generate a new Next.js project using the App Router, TypeScript, and Tailwind CSS, and then initialize <code>shadcn\/ui<\/code> for your component library.<\/p>\n<h3>1. Create the Next.js App<\/h3>\n<p>Open your terminal and run the <code>create-next-app<\/code> command to bootstrap the project. Name it <code>ai-content-engine<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npx<\/span> <span class=\"hljs-selector-tag\">create-next-app<\/span><span class=\"hljs-keyword\">@latest<\/span> ai-content-engine --typescript --tailwind --eslint --use-npm\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>2. Initialize <code>shadcn\/ui<\/code><\/h3>\n<p>Once the project is created, navigate into the directory and initialize <code>shadcn\/ui<\/code>. This CLI tool will automatically configure your <code>tailwind.config.ts<\/code>, global styles, and utility functions.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">cd<\/span> <span class=\"hljs-selector-tag\">ai-content-engine<\/span>\n<span class=\"hljs-selector-tag\">npx<\/span> <span class=\"hljs-selector-tag\">shadcn<\/span><span class=\"hljs-keyword\">@latest<\/span> init\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Follow the interactive prompts and accept the defaults for a standard setup so you\u2019ll have a clean, consistent structure to add your UI components later.<\/p>\n<p>With the project initialized, your <code>app\/layout.tsx<\/code> file provides a clean slate, ready for you to build upon.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In app\/layout.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> type { Metadata } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Inter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/font\/google\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">\".\/globals.css\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> inter = Inter({ <span class=\"hljs-attr\">subsets<\/span>: &#91;<span class=\"hljs-string\">\"latin\"<\/span>] });\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> metadata: Metadata = {\n  <span class=\"hljs-attr\">title<\/span>: <span class=\"hljs-string\">\"AI Content Engine\"<\/span>,\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">RootLayout<\/span>(<span class=\"hljs-params\">{\n  children,\n}: {\n  children: React.ReactNode,\n}<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html<\/span> <span class=\"hljs-attr\">lang<\/span>=<span class=\"hljs-string\">\"en\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>&gt;<\/span>{children}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">html<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>You can view the full file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/app\/layout.tsx\"><code>app\/layout.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<p>With your project\u2019s foundation in place, you\u2019re ready to set up the backend.<\/p>\n<h2>Setting the Backend Foundation<\/h2>\n<p>With your project\u2019s structure in place, it\u2019s time to build the engine that will power your application. This involves securely managing our API keys, establishing a \u201crulebook\u201d for our AI, and running the local Cloudinary MCP server that will execute your media transformations.<\/p>\n<h3>Configuring Secure Environment Variables<\/h3>\n<p>To protect your secret keys, avoid hardcoding them in your application. Use an environment file (<code>.env.local<\/code>) that Next.js automatically loads on the server. This file is included in <code>.gitignore<\/code> by default, which ensures your keys are never committed to your repository.<\/p>\n<p>In the root of your project, create a file named <code>.env.local<\/code>. Then populate the file with the credentials from your Cloudinary, Pexels, and OpenAI dashboards.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># .env.local<\/span>\n\n<span class=\"hljs-comment\"># Cloudinary Credentials<\/span>\nCLOUDINARY_CLOUD_NAME=<span class=\"hljs-string\">\"YOUR_CLOUD_NAME\"<\/span>\nCLOUDINARY_API_KEY=<span class=\"hljs-string\">\"YOUR_API_KEY\"<\/span>\nCLOUDINARY_API_SECRET=<span class=\"hljs-string\">\"YOUR_API_SECRET\"<\/span>\n\n<span class=\"hljs-comment\"># Pexels API Key<\/span>\nPEXELS_API_KEY=<span class=\"hljs-string\">\"YOUR_PEXELS_API_KEY\"<\/span>\n\n<span class=\"hljs-comment\"># OpenAI API Key<\/span>\nOPENAI_API_KEY=<span class=\"hljs-string\">\"YOUR_OPENAI_API_KEY\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Adding Cloudinary\u2019s Transformation Rules<\/h3>\n<p>To ensure your AI generates only valid Cloudinary transformations, you\u2019ll have to provide it with a set of rules. Download a markdown file from Cloudinary\u2019s documentation that contains all the valid parameters and syntax. This file will serve as the context for your AI, preventing it from hallucinating incorrect commands.<\/p>\n<p>Run the following command in your terminal to download the file into your project root:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">curl -o cloudinary_transformation_rules.md https:<span class=\"hljs-comment\">\/\/cloudinary.com\/documentation\/cloudinary_transformation_rules.md<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Running the Cloudinary MCP Server Locally<\/h3>\n<p>The core of your media backend is the Cloudinary MCP Server. Run it locally using a script that leverages <code>supergateway<\/code>, a tool that exposes the MCP server\u2019s command-line interface over a standard HTTP endpoint. This allows your Next.js application to communicate with it.<\/p>\n<h4>1. Create the Server Script<\/h4>\n<p>Create a new file at <code>scripts\/start-mcp-asset.ts<\/code>. This script will start the <code>@cloudinary\/asset-management<\/code> MCP tool and wrap it in the gateway.<\/p>\n<p>The main execution part of the script uses Node.js\u2019s <code>spawn<\/code> to run the <code>supergateway<\/code> command with the necessary arguments.<\/p>\n<pre><code>```tsx\n\/\/ In scripts\/start-mcp-asset.ts\n\/\/ ... (helper functions for getting credentials and checking health)\n\nasync function main() {\n  \/\/ ...\n  const cmd = &quot;npx&quot;;\n  const args = [\n    &quot;-y&quot;,\n    &quot;supergateway&quot;,\n    &quot;--port&quot;,\n    String(PORT),\n    &quot;--ssePath&quot;,\n    &quot;\/sse&quot;,\n    &quot;--messagePath&quot;,\n    &quot;\/message&quot;,\n    &quot;--stdio&quot;,\n    &quot;npx -y --package @cloudinary\/asset-management -- mcp start&quot;,\n  ];\n  const child = spawn(cmd, args, {\n    stdio: &quot;inherit&quot;,\n    env: { ...process.env, CLOUDINARY_URL: cloudinaryUrl },\n  });\n  \/\/ ... (health check logic)\n}\n```\n<\/code><\/pre>\n<blockquote>\n<p>You can view the full script file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/scripts\/start-mcp-asset.ts\"><code>scripts\/start-mcp-asset.ts<\/code><\/a><\/p>\n<\/blockquote>\n<h4>2. Add Dependencies and Run Script<\/h4>\n<p>You\u2019ll need two packages to run this script: <code>dotenv<\/code> to load your environment variables and <code>tsx<\/code> to execute TypeScript files directly.<\/p>\n<pre><code>```bash\nnpm install dotenv\nnpm install --save-dev tsx\n```\n\nNext, open your `package.json` file and add a script to easily run the server:\n\n```json\n\/\/ In package.json\n&quot;scripts&quot;: {\n  &quot;dev&quot;: &quot;next dev&quot;,\n  &quot;build&quot;: &quot;next build&quot;,\n  &quot;start&quot;: &quot;next start&quot;,\n  &quot;lint&quot;: &quot;eslint&quot;,\n  &quot;start-mcp&quot;: &quot;tsx scripts\/start-mcp-asset.ts&quot;\n},\n```\n<\/code><\/pre>\n<p>Now, you can run <code>npm run start-mcp<\/code> in a terminal to start the local gateway. With the backend server running, we\u2019re ready to build the API that will command it.<\/p>\n<h2>Creating the Generation API<\/h2>\n<p>With the backend server running, you\u2019ll need an API endpoint to act as the central orchestrator. This API route will receive the user\u2019s prompt, coordinate with Pexels and OpenAI, and command the local MCP server to perform the final media manipulation.<\/p>\n<h2>Introducing the MCP SDK for Robust Connections<\/h2>\n<p>Our initial attempts to communicate with the MCP gateway via a simple <code>fetch<\/code> request failed because the gateway expects a persistent, stateful connection using Server-Sent Events (SSE). To handle this correctly, we must use the official <code>@modelcontextprotocol\/sdk<\/code>. This library abstracts away the complexities of establishing and managing the connection.<\/p>\n<p>First, install the SDK in your project:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-keyword\">@modelcontextprotocol<\/span>\/sdk\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next, create a helper function to manage the connection logic. Create a new file at <code>lib\/mcp-client.ts<\/code>. This module will contain a <code>connectCloudinary<\/code> function that initializes the MCP client, establishes the SSE transport layer, and connects to our local gateway.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In lib\/mcp-client.ts<\/span>\n<span class=\"hljs-keyword\">import<\/span> { Client } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@modelcontextprotocol\/sdk\/client\/index.js\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { SSEClientTransport } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@modelcontextprotocol\/sdk\/client\/sse.js\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> MCP_GATEWAY_URL = <span class=\"hljs-string\">\"http:\/\/localhost:8787\/sse\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">connectCloudinary<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> client = <span class=\"hljs-keyword\">new<\/span> Client({\n    <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"ai-content-engine-client\"<\/span>,\n    <span class=\"hljs-attr\">version<\/span>: <span class=\"hljs-string\">\"0.1.0\"<\/span>,\n  });\n  <span class=\"hljs-keyword\">const<\/span> transport = <span class=\"hljs-keyword\">new<\/span> SSEClientTransport(<span class=\"hljs-keyword\">new<\/span> URL(MCP_GATEWAY_URL));\n  <span class=\"hljs-keyword\">await<\/span> client.connect(transport);\n  <span class=\"hljs-keyword\">return<\/span> client;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This simple helper is the key to successfully communicating with our local server.<\/p>\n<blockquote>\n<p>You can view the full file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/lib\/mcp-client.ts\"><code>lib\/mcp-client.ts<\/code><\/a><\/p>\n<\/blockquote>\n<h3>Building the API Route (<code>\/api\/generate<\/code>)<\/h3>\n<p>Now, let\u2019s create the main API route at <code>app\/api\/generate\/route.ts<\/code>. This server-side function will execute our entire workflow in a series of sequential steps.<\/p>\n<h4>1. Fetch an Image From Pexels<\/h4>\n<p>The first step is to take the user\u2019s prompt and find a suitable source image. Let\u2019s use the official <code>pexels<\/code> client library to search for a photo.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In app\/api\/generate\/route.ts<\/span>\n<span class=\"hljs-keyword\">const<\/span> pexelsClient = createClient(process.env.PEXELS_API_KEY!);\n<span class=\"hljs-keyword\">const<\/span> photoResponse = <span class=\"hljs-keyword\">await<\/span> pexelsClient.photos.search({ <span class=\"hljs-attr\">query<\/span>: prompt, <span class=\"hljs-attr\">per_page<\/span>: <span class=\"hljs-number\">1<\/span> });\n<span class=\"hljs-keyword\">const<\/span> imageUrl = photoResponse.photos&#91;<span class=\"hljs-number\">0<\/span>].src.original;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h4>2. Consult the AI: Dynamically Generate Transformations<\/h4>\n<p>This is the core of your AI engine. You\u2019ll send the user\u2019s full prompt and your <code>cloudinary_transformation_rules.md<\/code> file to the OpenAI API. Next, instruct the model to act as a silent API that <em>only<\/em> returns a valid Cloudinary transformation string based on the rules provided.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In app\/api\/generate\/route.ts<\/span>\n<span class=\"hljs-keyword\">const<\/span> rules = getTransformationRules(); <span class=\"hljs-comment\">\/\/ Reads the markdown file<\/span>\n<span class=\"hljs-keyword\">const<\/span> llmResponse = <span class=\"hljs-keyword\">await<\/span> openai.chat.completions.create({\n  <span class=\"hljs-attr\">model<\/span>: <span class=\"hljs-string\">\"gpt-4\"<\/span>,\n  <span class=\"hljs-attr\">messages<\/span>: &#91;\n    {\n      <span class=\"hljs-attr\">role<\/span>: <span class=\"hljs-string\">\"system\"<\/span>,\n      <span class=\"hljs-attr\">content<\/span>: <span class=\"hljs-string\">`You are a silent API... The rules are:\\n\\n<span class=\"hljs-subst\">${rules}<\/span>`<\/span>,\n    },\n    {\n      <span class=\"hljs-attr\">role<\/span>: <span class=\"hljs-string\">\"user\"<\/span>,\n      <span class=\"hljs-attr\">content<\/span>: <span class=\"hljs-string\">`Prompt: \"<span class=\"hljs-subst\">${prompt}<\/span>\". Respond with only the transformation string.`<\/span>,\n    },\n  ],\n});\n<span class=\"hljs-keyword\">const<\/span> dynamicTransformation = llmResponse.choices&#91;<span class=\"hljs-number\">0<\/span>]?.message?.content?.trim();\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h4>3. Command the MCP Server<\/h4>\n<p>Finally, connect to the local MCP server using your <code>connectCloudinary<\/code> helper. Then use the <code>client.callTool<\/code> method to execute the <code>upload-asset<\/code> command, passing in the Pexels image URL and the AI-generated transformation string.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In app\/api\/generate\/route.ts<\/span>\nmcpClient = <span class=\"hljs-keyword\">await<\/span> connectCloudinary();\n<span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> mcpClient.callTool({\n  <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"upload-asset\"<\/span>,\n  <span class=\"hljs-attr\">arguments<\/span>: {\n    <span class=\"hljs-attr\">uploadRequest<\/span>: {\n      <span class=\"hljs-attr\">file<\/span>: imageUrl,\n      <span class=\"hljs-attr\">transformation<\/span>: dynamicTransformation,\n      <span class=\"hljs-attr\">folder<\/span>: <span class=\"hljs-string\">\"ai-content-engine-gallery\"<\/span>,\n      <span class=\"hljs-attr\">context<\/span>: <span class=\"hljs-string\">`pexels_url=<span class=\"hljs-subst\">${imageUrl}<\/span>`<\/span>,\n    },\n  },\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The API then parses the successful response from the gateway and returns the final, transformed image URL to the frontend.<\/p>\n<blockquote>\n<p>You can view the full API route file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/app\/api\/generate\/route.ts\"><code>app\/api\/generate\/route.ts<\/code><\/a><\/p>\n<\/blockquote>\n<h2>Building the User Interface<\/h2>\n<p>A powerful backend deserves an intuitive frontend. You\u2019ll create a professional, app-like experience by building a sticky layout with a dedicated navigation bar and footer. The main content area will comprise smaller components, each handling a specific piece of functionality. Start by adding a <strong>navigation bar<\/strong> and <strong>footer<\/strong> to give the app a professional and consistent structure.<\/p>\n<p>The <strong>navbar<\/strong> displays the app title and links like \u201cHome\u201d and \u201cGallery.\u201d It\u2019s responsive and adjusts smoothly for mobile screens.<\/p>\n<p>The <strong>Footer<\/strong> shows simple copyright text and credits for Cloudinary, Pexels, and OpenAI.<\/p>\n<p>These two components frame the app visually, keeping navigation clear and consistent across pages.<\/p>\n<p>You can view both files on GitHub:<\/p>\n<ul>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/components\/Navbar.tsx\"><code>components\/Navbar.tsx<\/code><\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/components\/Footer.tsx\"><code>components\/Footer.tsx<\/code><\/a>\n<\/li>\n<\/ul>\n<h2>Structuring the Main Layout for a Professional Feel<\/h2>\n<p>To create the sticky layout, you\u2019ll modify the root layout file to use flexbox. This will fix the <code>Navbar<\/code> to the top and the <code>Footer<\/code> to the bottom, allowing only the main content area to scroll.<\/p>\n<p>Update your <code>app\/layout.tsx<\/code> file to import and use these new components.<\/p>\n<p><strong>Core layout:<\/strong><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In app\/layout.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> { Navbar } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/Navbar\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Footer } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/Footer\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { cn } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/utils\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">RootLayout<\/span>(<span class=\"hljs-params\">{\n  children,\n}: {\n  children: React.ReactNode,\n}<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html<\/span> <span class=\"hljs-attr\">lang<\/span>=<span class=\"hljs-string\">\"en\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>\n        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{cn(<\/span>\n          \"<span class=\"hljs-attr\">min-h-screen<\/span> <span class=\"hljs-attr\">bg-background<\/span> <span class=\"hljs-attr\">font-sans<\/span> <span class=\"hljs-attr\">antialiased<\/span> <span class=\"hljs-attr\">flex<\/span> <span class=\"hljs-attr\">flex-col<\/span>\"\n        )}\n      &gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex-grow container mx-auto px-4 py-8\"<\/span>&gt;<\/span>\n          {children}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Footer<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">html<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>You can view the full file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/app\/layout.tsx\"><code>app\/layout.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<h2>Creating the <code>PromptForm<\/code> and <code>GeneratedImage<\/code> Components<\/h2>\n<p>Next, you\u2019ll encapsulate the core functionality of your home page into dedicated components.<\/p>\n<ol>\n<li>\n<strong>PromptForm.<\/strong> Create a new file at <code>components\/PromptForm.tsx<\/code>. This component contains the main <code>Card<\/code>, the input field, and the generate button. It manages all the client-side state for handling the form submission, loading, and error states.<\/li>\n<\/ol>\n<p>The core logic resides in the <code>handleSubmit<\/code> function, which makes the <code>POST<\/code> request to our <code>\/api\/generate<\/code> endpoint.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In components\/PromptForm.tsx<\/span>\n<span class=\"hljs-string\">\"use client\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-comment\">\/\/ ... other imports<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">PromptForm<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;prompt, setPrompt] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;imageUrl, setImageUrl] = (useState &lt; string) | (<span class=\"hljs-literal\">null<\/span> &gt; <span class=\"hljs-literal\">null<\/span>);\n  <span class=\"hljs-comment\">\/\/ ... other state variables<\/span>\n\n  <span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-keyword\">async<\/span> (e: React.FormEvent) =&gt; {\n    e.preventDefault();\n    <span class=\"hljs-comment\">\/\/ ... (API call logic)<\/span>\n  };\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Card<\/span>&gt;<\/span>\n      {\/* ... Card Header *\/}\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>&gt;<\/span>\n        {\/* ... Card Content with Input *\/}\n        {\/* ... Card Footer with Button *\/}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n      {imageUrl &amp;&amp; !isLoading &amp;&amp; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">GeneratedImage<\/span> <span class=\"hljs-attr\">imageUrl<\/span>=<span class=\"hljs-string\">{imageUrl}<\/span> <span class=\"hljs-attr\">prompt<\/span>=<span class=\"hljs-string\">{prompt}<\/span> \/&gt;<\/span>\n      )}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Card<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>You can view the full file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/components\/PromptForm.tsx\"><code>components\/PromptForm.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<ol start=\"2\">\n<li>\n<strong>GeneratedImage.<\/strong> To make the UI even cleaner, you\u2019ll create a component at <code>components\/GeneratedImage.tsx<\/code> that is responsible for displaying the final image. This component also includes a dialog from <code>shadcn\/ui<\/code> that allows users to click the image to view a full-size version in a modal.<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">\/\/ In components\/GeneratedImage.tsx\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Dialog<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">DialogTrigger<\/span> <span class=\"hljs-attr\">asChild<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"cursor-pointer relative w-full h-96\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{imageUrl}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{prompt}<\/span> <span class=\"hljs-attr\">fill<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"object-cover rounded-lg\"<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">DialogTrigger<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">DialogContent<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-5xl\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{imageUrl}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{prompt}<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{1200}<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{800}<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"object-contain rounded-lg\"<\/span> \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">DialogContent<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Dialog<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>You can view the full file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/components\/GeneratedImage.tsx\"><code>components\/GeneratedImage.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<h2>Showcasing Your Gallery<\/h2>\n<p>Your application can now generate content, but the results are only visible to the user who created them. To complete the experience, you\u2019ll need to build a public gallery to showcase all the generated images.<\/p>\n<p>Create a dedicated gallery page and add a preview section to our home page that displays the four most recent creations.<\/p>\n<h3>Storing Metadata: Saving the Pexels URL<\/h3>\n<p>To provide proper attribution and context, your gallery cards will display both the final Cloudinary URL and the original Pexels source URL.<\/p>\n<p>First, you\u2019ll need to update your generation API to save the Pexels URL as metadata when the image is uploaded. Use Cloudinary\u2019s <code>context<\/code> feature, which allows us to store custom key-value data with an asset.<\/p>\n<p>Update your <code>app\/api\/generate\/route.ts<\/code> file to add the <code>context<\/code> parameter to the <code>uploadRequest<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In app\/api\/generate\/route.ts<\/span>\n<span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> mcpClient.callTool({\n  <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"upload-asset\"<\/span>,\n  <span class=\"hljs-attr\">arguments<\/span>: {\n    <span class=\"hljs-attr\">uploadRequest<\/span>: {\n      <span class=\"hljs-attr\">file<\/span>: imageUrl,\n      <span class=\"hljs-attr\">transformation<\/span>: dynamicTransformation,\n      <span class=\"hljs-attr\">folder<\/span>: <span class=\"hljs-string\">\"ai-content-engine-gallery\"<\/span>,\n      <span class=\"hljs-attr\">context<\/span>: <span class=\"hljs-string\">`pexels_url=<span class=\"hljs-subst\">${imageUrl}<\/span>`<\/span>, <span class=\"hljs-comment\">\/\/ Store original URL in context<\/span>\n    },\n  },\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>View the full file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/app\/api\/generate\/route.ts\"><code>app\/api\/generate\/route.ts<\/code><\/a><\/p>\n<\/blockquote>\n<h3>Creating the Gallery API (<code>\/api\/gallery<\/code>)**<\/h3>\n<p>Next, you\u2019ll need a dedicated API endpoint to fetch all the images from your gallery folder.<\/p>\n<p>For this read-only task, you\u2019ll use the standard Cloudinary Node.js SDK, which is optimized for searching and retrieving asset data.<\/p>\n<p>Install the library:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install cloudinary\n<\/code><\/span><\/pre>\n<p>Create a new API route at <code>app\/api\/gallery\/route.ts<\/code>.<\/p>\n<p>This server-side route will use Cloudinary\u2019s Search API to find all assets in the <code>ai-content-engine-gallery<\/code> folder, sort them by creation date, and include the context metadata we just added.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In app\/api\/gallery\/route.ts<\/span>\n<span class=\"hljs-keyword\">import<\/span> { NextResponse } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/server\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n\ncloudinary.config({\n  <span class=\"hljs-comment\">\/* ... your credentials ... *\/<\/span>\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">GET<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> results = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n      .expression(<span class=\"hljs-string\">\"folder=ai-content-engine-gallery\"<\/span>)\n      .with_field(<span class=\"hljs-string\">\"context\"<\/span>) <span class=\"hljs-comment\">\/\/ Get the pexels_url<\/span>\n      .sort_by(<span class=\"hljs-string\">\"created_at\"<\/span>, <span class=\"hljs-string\">\"desc\"<\/span>)\n      .max_results(<span class=\"hljs-number\">30<\/span>)\n      .execute();\n\n    <span class=\"hljs-keyword\">const<\/span> galleryImages = results.resources.map(\n      <span class=\"hljs-function\">(<span class=\"hljs-params\">resource: CloudinaryResource<\/span>) =&gt;<\/span> ({\n        <span class=\"hljs-attr\">id<\/span>: resource.asset_id,\n        <span class=\"hljs-attr\">cloudinaryUrl<\/span>: resource.secure_url,\n        <span class=\"hljs-attr\">pexelsUrl<\/span>: resource.context?.pexels_url || <span class=\"hljs-literal\">null<\/span>,\n      })\n    );\n\n    <span class=\"hljs-keyword\">return<\/span> NextResponse.json(galleryImages);\n  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n    <span class=\"hljs-comment\">\/\/ Handle error<\/span>\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>View the full file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/app\/api\/gallery\/route.ts\"><code>app\/api\/gallery\/route.ts<\/code><\/a><\/p>\n<\/blockquote>\n<h3>Building the Reusable <code>GalleryCard<\/code> Component<\/h3>\n<p>To keep the frontend clean, create a <code>GalleryCard<\/code> component at <code>components\/gallery\/GalleryCard.tsx<\/code>. It displays one gallery item and includes copy-to-clipboard buttons for URLs using <code>shadcn\/ui<\/code> and Lucide icons.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In components\/gallery\/GalleryCard.tsx<\/span>\n<span class=\"hljs-string\">'use client'<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next\/image'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Button } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/components\/ui\/button'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Card, CardContent } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/components\/ui\/card'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Tooltip, TooltipProvider, TooltipTrigger } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/components\/ui\/tooltip'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Check, Copy } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'lucide-react'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">GalleryCard<\/span>(<span class=\"hljs-params\">{ image }: { image: GalleryImage }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Card<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"overflow-hidden ...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CardContent<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"p-0\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"relative aspect-square w-full\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{image.cloudinaryUrl}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"AI generated image\"<\/span> <span class=\"hljs-attr\">fill<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"p-3 space-y-2\"<\/span>&gt;<\/span>\n          {\/* LinkRow for Cloudinary URL *\/}\n          {\/* LinkRow for Pexels URL *\/}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">CardContent<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Card<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>View the full file on GitHub: <a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/components\/gallery\/GalleryCard.tsx\"><code>components\/gallery\/GalleryCard.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<h3>Assembling the Gallery and Home Page<\/h3>\n<h4>1. Gallery Page<\/h4>\n<p>Create <code>app\/gallery\/page.tsx<\/code> as a <strong>React Server Component<\/strong>.<\/p>\n<p>It fetches data from <code>\/api\/gallery<\/code> and renders a <code>GalleryCard<\/code> for each image.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">\/\/ In app\/gallery\/page.tsx\nimport { GalleryCard } from \"@\/components\/gallery\/GalleryCard\";\n\nasync function getGalleryImages(): Promise<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">GalleryImage&#91;]<\/span>&gt;<\/span> {\n  \/\/ ... server-side fetching logic ...\n}\n\nexport default async function GalleryPage() {\n  const images = await getGalleryImages();\n\n  return (\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n      {\/* Page header *\/}\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 ...\"<\/span>&gt;<\/span>\n        {images.map((image) =&gt; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">GalleryCard<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{image.id}<\/span> <span class=\"hljs-attr\">image<\/span>=<span class=\"hljs-string\">{image}<\/span> \/&gt;<\/span>\n        ))}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h4>2. Latest Images Preview<\/h4>\n<p>Create <code>components\/home\/LatestImages.tsx<\/code> to fetch images like the gallery page but limit results to the latest four.<\/p>\n<h4>3. Updating the Home Page<\/h4>\n<p>Update <code>app\/page.tsx<\/code> to include <code>LatestImages<\/code> inside a <code>&lt;Suspense&gt;<\/code> boundary for smooth loading.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ In app\/page.tsx<\/span>\n<span class=\"hljs-keyword\">import<\/span> PromptForm <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/components\/PromptForm'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { LatestImages } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/components\/home\/LatestImages'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Suspense } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">HomePage<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col items-center ...\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full max-w-2xl\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">PromptForm<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Suspense<\/span> <span class=\"hljs-attr\">fallback<\/span>=<span class=\"hljs-string\">{<\/span>&lt;<span class=\"hljs-attr\">div<\/span>&gt;<\/span>Loading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>}&gt;\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">LatestImages<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Suspense<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>View the full files on GitHub:<\/p>\n<ul>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/app\/gallery\/page.tsx\"><code>app\/gallery\/page.tsx<\/code><\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/components\/home\/LatestImages.tsx\"><code>components\/home\/LatestImages.tsx<\/code><\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/github.com\/musebe\/ai-content-engine\/blob\/main\/app\/page.tsx\"><code>app\/page.tsx<\/code><\/a>\n<\/li>\n<\/ul>\n<h2>Conclusion and Next Steps<\/h2>\n<p>And there you have it, a complete, intelligent media pipeline built from scratch. We\u2019ve successfully bridged the gap between a simple text prompt and a production-ready visual asset. By combining the power of a modern web framework like <strong>Next.js<\/strong>, the dynamic intelligence of the <strong>OpenAI API<\/strong>, and the robust media command interface of <strong>Cloudinary\u2019s MCP Server<\/strong>, we\u2019ve built an application that is more than just a proof of concept; it\u2019s a new paradigm for content creation.<\/p>\n<p>This application is a fantastic foundation, but the journey doesn\u2019t have to end here. You can expand upon this project in many exciting ways:<\/p>\n<ul>\n<li>\n<strong>Expand to video transformations.<\/strong> Adapt the AI prompt and MCP commands to handle video assets, applying effects, trimming clips, or generating subtitles.<\/li>\n<li>\n<strong>Create a transformation \u201cmemory.\u201d<\/strong> Allow users to save their favorite AI-generated transformation strings as presets for future use.<\/li>\n<li>\n<strong>Integrate different AI models.<\/strong> Experiment with other large language models or specialized image analysis models to extract more context from the source image and apply even smarter transformations.<\/li>\n<li>\n<strong>Build an admin dashboard.<\/strong> Create a private page that allows an administrator to view all generated images and curate the public gallery.<\/li>\n<\/ul>\n<p><a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up<\/a> for a free Cloudinary account today to get started.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":39090,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[424,409,370,212,304],"class_list":["post-39089","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-agentic","tag-generative-ai","tag-image","tag-next-js","tag-video-transformation"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building an AI-Powered Content Engine With Cloudinary&#039;s MCP Server and Next.js<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building an AI-Powered Content Engine With Cloudinary&#039;s MCP Server and Next.js\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-11-12T15:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-12-12T23:33:12+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Building an AI-Powered Content Engine With Cloudinary&#8217;s MCP Server and Next.js\",\"datePublished\":\"2025-11-12T15:00:00+00:00\",\"dateModified\":\"2025-12-12T23:33:12+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js\"},\"wordCount\":13,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA\",\"keywords\":[\"Agentic\",\"Generative AI\",\"Image\",\"Next.js\",\"Video Transformation\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js\",\"url\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js\",\"name\":\"Building an AI-Powered Content Engine With Cloudinary's MCP Server and Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA\",\"datePublished\":\"2025-11-12T15:00:00+00:00\",\"dateModified\":\"2025-12-12T23:33:12+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building an AI-Powered Content Engine With Cloudinary&#8217;s MCP Server and Next.js\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Building an AI-Powered Content Engine With Cloudinary's MCP Server and Next.js","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js","og_locale":"en_US","og_type":"article","og_title":"Building an AI-Powered Content Engine With Cloudinary's MCP Server and Next.js","og_url":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js","og_site_name":"Cloudinary Blog","article_published_time":"2025-11-12T15:00:00+00:00","article_modified_time":"2025-12-12T23:33:12+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Building an AI-Powered Content Engine With Cloudinary&#8217;s MCP Server and Next.js","datePublished":"2025-11-12T15:00:00+00:00","dateModified":"2025-12-12T23:33:12+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js"},"wordCount":13,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA","keywords":["Agentic","Generative AI","Image","Next.js","Video Transformation"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js","url":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js","name":"Building an AI-Powered Content Engine With Cloudinary's MCP Server and Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA","datePublished":"2025-11-12T15:00:00+00:00","dateModified":"2025-12-12T23:33:12+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/ai-powered-content-engine-cloudinarys-mcp-server-next-js#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building an AI-Powered Content Engine With Cloudinary&#8217;s MCP Server and Next.js"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761940428\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels\/Blog_Building_an_AI-Powered_Content_Engine_with_Cloudinary_s_MCP_server_and_Pexels.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39089","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=39089"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39089\/revisions"}],"predecessor-version":[{"id":39091,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39089\/revisions\/39091"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/39090"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=39089"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=39089"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=39089"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}