{"id":38682,"date":"2025-10-21T07:00:00","date_gmt":"2025-10-21T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=38682"},"modified":"2025-10-21T09:21:15","modified_gmt":"2025-10-21T16:21:15","slug":"ai-powered-smart-media-uploader-optimizer-mcp-server-next-js","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js","title":{"rendered":"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in Next.js"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p><a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\"><strong>GitHub Repository<\/strong><\/a><\/p>\n<iframe loading=\"lazy\"\n  src=\"https:\/\/player.cloudinary.com\/embed\/?cloud_name=hackit-africa&#038;public_id=chat_uploads%2Fdemo&#038;profile=cld-default\"\n  width=\"640\"\n  height=\"360\" \n  style=\"height: auto; width: 100%; aspect-ratio: 640 \/ 360;\"\n  allow=\"autoplay; fullscreen; encrypted-media; picture-in-picture\"\n  allowfullscreen\n  frameborder=\"0\"\n><\/iframe>\n<p>Imagine managing your entire media library not by clicking through folders and forms, but by having a conversation. Instead of manually searching, selecting, and tagging assets, you could simply type: \u201c<em><strong>Find all images from the \u2018summer-sale\u2019 folder and tag them with \u2018archive-2025\u2019<\/strong><\/em>.\u201d This is the future of digital asset management: an intelligent, conversational interface that works like a co-pilot for your creative workflow.<\/p>\n<p>This platform provides a powerful chat interface that understands your commands, manages your media, and even handles new uploads. It\u2019s all powered by a cutting-edge, full-stack setup: a Next.js frontend, OpenAI for natural language understanding, and the <a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_llm_mcp\">Cloudinary Model Context Protocol<\/a> (MCP) Server acting as the bridge between them.<\/p>\n<p>In this tutorial, you\u2019ll build a complete AI-powered media assistant that can:<\/p>\n<ol>\n<li>\n<strong>Launch a local Cloudinary MCP gateway<\/strong> that exposes powerful asset management tools in a way that AI models can understand.<\/li>\n<li>\n<strong>Provide a polished Next.js chat interface<\/strong> for uploading files and interacting with your media library.<\/li>\n<li>\n<strong>Process natural language commands<\/strong> to list, rename, move, tag, and delete assets in your Cloudinary account.<\/li>\n<li>\n<strong>Integrate with OpenAI<\/strong> to intelligently interpret user requests and call the appropriate Cloudinary tools.<\/li>\n<\/ol>\n<p>Let\u2019s dive in!<\/p>\n<h2>Laying the Foundation: Project Setup and Environment<\/h2>\n<p>Before we dive into the code, we need to get the project running on your local machine. This involves cloning the starter repository, installing the necessary packages, and configuring your environment with the required API keys for Cloudinary and OpenAI.<\/p>\n<h3>1. Clone the Project<\/h3>\n<p>We\u2019ll start by cloning the complete project from GitHub. This gives us the full application structure right away.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">git <span class=\"hljs-keyword\">clone<\/span> https:<span class=\"hljs-comment\">\/\/github.com\/musebe\/cloudinary-mcp-media-assistant.git<\/span>\n\ncd cloudinary-mcp-media-assistant\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><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>2. Install Dependencies<\/h3>\n<p>Next, install all the required Node.js packages using <code>npm<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install\n<\/code><\/span><\/pre>\n<p>This will install Next.js, React, the Cloudinary and OpenAI SDKs, and other utilities defined in the <code>package.json<\/code> file.<\/p>\n<h3>3. Configure Environment Variables<\/h3>\n<p>The assistant needs to connect to your specific Cloudinary and OpenAI accounts. We\u2019ll store these secret keys in a local environment file that should <strong>never<\/strong> be committed to version control.<\/p>\n<p>Create a new file named <code>.env.local<\/code> in the root of your project and add the following content:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># Get this from your Cloudinary Dashboard homepage<\/span>\n<span class=\"hljs-comment\"># Format: cloudinary:\/\/API_KEY:API_SECRET@CLOUD_NAME<\/span>\nCLOUDINARY_URL=<span class=\"hljs-string\">\"your_cloudinary_url\"<\/span>\n\n<span class=\"hljs-comment\"># Get this from platform.openai.com\/api-keys<\/span>\nOPENAI_API_KEY=<span class=\"hljs-string\">\"sk-...\"<\/span>\n\n<span class=\"hljs-comment\"># Port for the local MCP server (optional, defaults to 8787)<\/span>\nMCP_PORT=<span class=\"hljs-number\">8787<\/span>\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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<ul>\n<li>\n<strong><code>CLOUDINARY_URL<\/code>.<\/strong> This single URL contains your Cloud Name, API Key, and API Secret. You can find it on your main <a href=\"https:\/\/cloudinary.com\/console\">Cloudinary Dashboard<\/a>.<\/li>\n<li>\n<strong><code>OPENAI_API_KEY<\/code>.<\/strong> This is required to give your assistant its \u201cbrain.\u201d You can generate a new key from your <a href=\"https:\/\/platform.openai.com\/api-keys\">OpenAI API Keys<\/a> page. The AI features are optional, but this key is needed to run the app as-is.<\/li>\n<\/ul>\n<blockquote>\n<p><strong>Important:<\/strong> Your <code>.env.local<\/code> file contains sensitive credentials. The project\u2019s <code>.gitignore<\/code> file is already configured to exclude it, but always ensure you don\u2019t accidentally expose your keys.<\/p>\n<\/blockquote>\n<p>With the setup complete, we\u2019re ready to start the engine.<\/p>\n<h2>The Engine Room: Launching the Cloudinary MCP Gateway<\/h2>\n<p>At the heart of our application is the <strong>Cloudinary MCP Gateway<\/strong>. Before we can build a chat interface, we need to run this local server. It acts as a crucial translator, converting Cloudinary\u2019s powerful Asset Management API into a standardized format that AI models can understand and interact with. This format is called the <strong>Model Context Protocol (MCP)<\/strong>.<\/p>\n<p>Instead of a traditional REST API, the MCP server exposes functions like <code>list-images<\/code> or <code>delete-asset<\/code> as \u201ctools\u201d that an AI can be instructed to use.<\/p>\n<h3>1. Start the MCP Server<\/h3>\n<p>The project includes a custom script to make starting this server simple. It handles configuration, starts the process, and checks that it\u2019s running correctly before finishing.<\/p>\n<p>Open a <strong>new, dedicated terminal window<\/strong> and run the following command:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" 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\">run<\/span> <span class=\"hljs-selector-tag\">dev<\/span><span class=\"hljs-selector-pseudo\">:mcp<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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>After a few moments, you should see output confirming that the server is active and listening for connections, ending with a success message.<\/p>\n<pre class=\"js-syntax-highlighted\"><code>\ud83d\udd39 Starting Cloudinary MCP Gateway...\n   Using CLOUDINARY_URL: \/\/***:***@your_cloud_name\n   Spawning process...\n\n\ud83e\ude7a Checking gateway health at http:\/\/localhost:8787\/sse...\n\u2705 Gateway is healthy and running!\n\n<\/code><\/pre>\n<p>Keep this terminal window open. This server must be running for the chat application to function.<\/p>\n<h3>2. Understanding the <code>start-mcp-asset.ts<\/code> Script<\/h3>\n<p>This isn\u2019t just a simple server command. It\u2019s a robust launcher. Let\u2019s look at two important parts of the <code>scripts\/start-mcp-asset.ts<\/code> file.<\/p>\n<p>First, the core command uses <code>npx<\/code> to run two packages together.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ scripts\/start-mcp-asset.ts (snippet)<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> cmd = <span class=\"hljs-string\">\"npx\"<\/span>;\n<span class=\"hljs-keyword\">const<\/span> args = &#91;\n  <span class=\"hljs-string\">\"-y\"<\/span>,\n  <span class=\"hljs-string\">\"supergateway\"<\/span>,\n  <span class=\"hljs-comment\">\/\/ ... port and path flags ...<\/span>\n  <span class=\"hljs-string\">\"--stdio\"<\/span>,\n  <span class=\"hljs-string\">\"npx -y --package @cloudinary\/asset-management -- mcp start\"<\/span>,\n];\n\n<span class=\"hljs-keyword\">const<\/span> child = spawn(cmd, args, {\n  <span class=\"hljs-comment\">\/* ... *\/<\/span>\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><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>Breakdown:<\/p>\n<ul>\n<li>\n<code>supergateway<\/code>. Utility that creates an MCP-compliant server from another process.<\/li>\n<li>\n<code>-stdio '...'<\/code>. Wraps the standard output of another command.<\/li>\n<li>\n<code>npx ... mcp start<\/code>. Runs the official Cloudinary asset management tools. By wrapping it, the gateway discovers all available Cloudinary functions (<code>list-images<\/code>, <code>upload-asset<\/code>, <code>asset-rename<\/code>, etc.) and exposes them as MCP tools.<\/li>\n<\/ul>\n<p>Next, the script includes a health check to ensure the server is actually ready before our app connects.<\/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\"><span class=\"hljs-comment\">\/\/ scripts\/start-mcp-asset.ts (snippet)<\/span>\n\n<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">checkHealth<\/span>(<span class=\"hljs-params\"><\/span>): <span class=\"hljs-title\">Promise<\/span>&lt;<span class=\"hljs-title\">boolean<\/span>&gt; <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> startTime = <span class=\"hljs-built_in\">Date<\/span>.now();\n  <span class=\"hljs-keyword\">while<\/span> (<span class=\"hljs-built_in\">Date<\/span>.now() - startTime &lt; HEALTH_CHECK_TIMEOUT) {\n    <span class=\"hljs-keyword\">try<\/span> {\n      <span class=\"hljs-comment\">\/\/ The \/sse path is the Server-Sent Events endpoint<\/span>\n      <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(HEALTH_CHECK_URL, { <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"GET\"<\/span> });\n      <span class=\"hljs-keyword\">if<\/span> (response.ok) {\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">true<\/span>; <span class=\"hljs-comment\">\/\/ Success!<\/span>\n      }\n    } <span class=\"hljs-keyword\">catch<\/span> {\n      <span class=\"hljs-comment\">\/\/ Ignore errors, server is still starting<\/span>\n    }\n    <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolve<\/span>) =&gt;<\/span> setTimeout(resolve, HEALTH_CHECK_INTERVAL));\n  }\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">false<\/span>; <span class=\"hljs-comment\">\/\/ Timeout<\/span>\n}\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<p>This loop prevents race conditions and ensures a smooth developer experience.<\/p>\n<blockquote>\n<p>For a deeper dive into how the protocol works, check out the official Cloudinary MCP Documentation.<\/p>\n<\/blockquote>\n<p>With our engine running, it\u2019s time to build the cockpit.<\/p>\n<h3>Building the Cockpit: Crafting the Conversational UI in Next.js<\/h3>\n<p>With our MCP server running, we need an interface for our conversation. The \u201ccockpit\u201d of our application is a clean chat UI built with Next.js, responsible for displaying messages and capturing user input.<\/p>\n<h3>1. The Main Hub: <code>ChatContainer.tsx<\/code><\/h3>\n<p>This component orchestrates the UI, primarily by managing and displaying the list of messages. Its main role is to map over the message state and render the appropriate components for each one.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/components\/chat\/chat-container.tsx (Conceptual Snippet)<\/span>\n<span class=\"hljs-comment\">\/\/ The container's core job is to render the list of messages.<\/span>\n&lt;ScrollArea&gt;\n  {optimisticMessages.map((m) =&gt; (\n    &lt;MessageBubble role={m.role}&gt;\n      {<span class=\"hljs-comment\">\/* Renders text, AssetLists, etc. inside *\/<\/span>}\n    &lt;\/MessageBubble&gt;\n  ))}\n&lt;\/ScrollArea&gt;;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><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<blockquote>\n<p>See the full component with state management on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/components\/chat\/chat-container.tsx\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<h3>2. Visualizing Media: <code>AssetList.tsx<\/code><\/h3>\n<p>When the assistant returns media assets, this component renders them in a rich list. Its most important job is displaying a thumbnail and key information for each asset.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">\/\/ src\/components\/chat\/asset-list.tsx (Snippet)\n\/\/ A simplified view of how an asset is displayed\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex items-start gap-3\"<\/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\">{item.thumbUrl}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"...\"<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{56}<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{56}<\/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\">\"font-medium\"<\/span> <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">{item.id}<\/span>&gt;<\/span>\n    {item.id}\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>&gt;<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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<p>See the full component on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/components\/chat\/asset-list.tsx\">GitHub<\/a>.<\/p>\n<h3>3. Capturing User Input: <code>ChatInput.tsx<\/code><\/h3>\n<p>This form handles both text and file inputs. The key is a visually hidden <code>input type=&quot;file&quot;<\/code> that is triggered by a button click, providing a clean UI for two types of actions.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">\/\/ src\/components\/chat\/chat-input.tsx (Snippet)\n\/\/ The mechanism for dual text\/file input\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">ref<\/span>=<span class=\"hljs-string\">{fileInputRef}<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"file\"<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"hidden\"<\/span> \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> fileInputRef.current?.click()}&gt;\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Paperclip<\/span> \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Button<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Input<\/span> <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Type a message or upload...\"<\/span> \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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>See the full component on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/components\/chat\/chat-input.tsx\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<p>With the user interface in place, we now need to wire it up to our backend logic.<\/p>\n<h2>Opening the Comms Channel: Connecting the UI to the Backend With Server Actions<\/h2>\n<p>Now that we have a UI and a server, we need to connect them. Instead of building traditional API routes, we\u2019ll use a modern Next.js feature: <strong>Server Actions<\/strong>. These are special functions that run on the server but can be called directly from our client components, making form submissions and data mutations simple and secure.<\/p>\n<h3>1. Wiring Up the Action in the UI<\/h3>\n<p>In our <code>ChatContainer.tsx<\/code> component, we use the <code>useActionState<\/code> hook from React. This hook is designed to work seamlessly with Server Actions.<\/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\">\/\/ src\/components\/chat\/chat-container.tsx (Snippet)<\/span>\n<span class=\"hljs-string\">\"use client\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useActionState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { sendMessageAction } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/(chat)\/actions\"<\/span>;\n<span class=\"hljs-comment\">\/\/ ...<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ChatContainer<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;messages, formAction, isPending] = useActionState(\n    sendMessageAction, <span class=\"hljs-comment\">\/\/ Our Server Action<\/span>\n    &#91;] <span class=\"hljs-comment\">\/\/ The initial state (an empty message list)<\/span>\n  );\n\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n}\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<p>This line does three things:<\/p>\n<ul>\n<li>\n<strong><code>messages<\/code>.<\/strong> Provides the updated list of chat messages returned by the action.<\/li>\n<li>\n<strong><code>formAction<\/code>.<\/strong> Exposes a function to trigger the action, which we pass to <code>ChatInput<\/code>.<\/li>\n<li>\n<strong><code>isPending<\/code>.<\/strong> Returns a boolean loading state, used to show a \u201ctyping\u201d bubble while the server processes the request.<\/li>\n<\/ul>\n<blockquote>\n<p>See the full implementation on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/components\/chat\/chat-container.tsx\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<h3>2. Defining the Server Action<\/h3>\n<p>The <code>sendMessageAction<\/code> function lives in <code>src\/app\/(chat)\/actions.ts<\/code>. The file starts with a <code>'use server';<\/code> directive, which enables the feature. The function accepts form data and the previous state from the hook.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">\/\/ src\/app\/(chat)\/actions.ts (Snippet)\n'use server';\n\nimport type { ChatMessage } from '@\/types';\n\nexport async function sendMessageAction(\n  previousState: ChatMessage&#91;] | null,\n  formData: FormData\n): Promise<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ChatMessage&#91;]<\/span>&gt;<\/span> {\n  \/\/ 1. Get user input from formData.\n  const text = formData.get('text') as string;\n  const file = formData.get('file');\n\n  \/\/ 2. Connect to the MCP server.\n  \/\/ 3. Call the correct tool based on the input.\n  \/\/ 4. Return the new message list.\n\n  \/\/ ... The full implementation follows ...\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\">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<p>This function is the central hub of the application\u2019s logic. It reads the <code>FormData<\/code> object from the client, determines whether the user uploaded a file or typed a command, and then calls the MCP server.<\/p>\n<blockquote>\n<p>See the full action file on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/app\/%28chat%29\/actions.ts\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<p>Now that the communication channel is open, we can implement the logic for handling specific user commands.<\/p>\n<h2>Handling Directives: Processing Text Commands With MCP Operations<\/h2>\n<p>Our Server Action is the central command hub. Now we\u2019ll implement the logic that interprets user commands and translates them into actions for our MCP server to execute. This involves a three-step process: <strong>parse intent<\/strong>, <strong>call an operation<\/strong>, and <strong>execute the MCP tool<\/strong>.<\/p>\n<h3>1. Parse User Intent With Regular Expressions<\/h3>\n<p>Inside <code>sendMessageAction<\/code>, we use regular expressions to understand the user\u2019s text. This is a fast and effective way to handle command-line-style instructions.<\/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\">\/\/ src\/app\/(chat)\/actions.ts (Snippet)<\/span>\n<span class=\"hljs-comment\">\/\/ A few examples of the intent-matching regex<\/span>\n<span class=\"hljs-keyword\">const<\/span> wantList = <span class=\"hljs-regexp\">\/^(list|show)\\s+images\/i<\/span>.test(text);\n<span class=\"hljs-keyword\">const<\/span> renameMatch = text.match(<span class=\"hljs-regexp\">\/^rename\\s+(.+?)\\s+to\\s+(.+)$\/i<\/span>);\n<span class=\"hljs-keyword\">const<\/span> deleteMatch = text.match(<span class=\"hljs-regexp\">\/^delete\\s+(.+)$\/i<\/span>);\n<span class=\"hljs-keyword\">const<\/span> tagMatch = text.match(<span class=\"hljs-regexp\">\/^tag\\s+(.+?)\\s+with\\s+(.+)$\/i<\/span>);\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<p>Based on which expression matches, we enter a specific block of logic to handle that command.<\/p>\n<blockquote>\n<p>See the full list of matchers in the action file on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/app\/%28chat%29\/actions.ts\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<h3>2. Handle the Command and Prepare a Response<\/h3>\n<p>Here\u2019s the handler for the <code>list images<\/code> command. It calls a dedicated operation function (<code>listImages<\/code>) and then uses the result to build the assistant\u2019s response.<\/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\">\/\/ src\/app\/(chat)\/actions.ts (Snippet)<\/span>\n<span class=\"hljs-comment\">\/\/ This block runs if the `wantList` regex matches<\/span>\n<span class=\"hljs-keyword\">if<\/span> (wantList) {\n  <span class=\"hljs-comment\">\/\/ 2a. Call the dedicated operation function<\/span>\n  <span class=\"hljs-keyword\">const<\/span> assets = <span class=\"hljs-keyword\">await<\/span> listImages(client);\n\n  <span class=\"hljs-comment\">\/\/ 2b. Build the assistant's reply object<\/span>\n  assistantMsg = {\n    <span class=\"hljs-attr\">id<\/span>: crypto.randomUUID(),\n    <span class=\"hljs-attr\">role<\/span>: <span class=\"hljs-string\">\"assistant\"<\/span>,\n    <span class=\"hljs-attr\">text<\/span>: assets.length ? <span class=\"hljs-string\">\"Here are your latest images:\"<\/span> : <span class=\"hljs-string\">\"No images found.\"<\/span>,\n    <span class=\"hljs-attr\">assets<\/span>: assets || <span class=\"hljs-literal\">undefined<\/span>, <span class=\"hljs-comment\">\/\/ This data is sent to the AssetList component<\/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<p>If assets are returned, they\u2019re attached to the <code>assets<\/code> property. The frontend <code>AssetList<\/code> component is designed to automatically render this data.<\/p>\n<h3>3. Execute the MCP Tool Call<\/h3>\n<p>The final step happens inside the operation functions in <code>src\/lib\/mcp-ops.ts<\/code>. These functions handle direct communication with the MCP server. The <code>listImages<\/code> function looks like this:<\/p>\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\">\/\/ src\/lib\/mcp-ops.ts (Snippet)\nexport async function listImages(client: MCPClient): Promise<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AssetItem&#91;]<\/span>&gt;<\/span> {\n  \/\/ 3a. Call the specific tool by name\n  const res = await client.callTool({ name: \"list-images\", arguments: {} });\n\n  if (res.isError) {\n    throw new Error(\"list-images failed\");\n  }\n\n  \/\/ 3b. Parse the raw JSON into our standardized AssetItem type\n  return toAssetsFromContent(res?.content || &#91;]) || &#91;];\n}\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<p>Here, <code>client.callTool({ name: 'list-images' })<\/code> sends the command to the MCP Gateway, which executes the corresponding Cloudinary function. The raw JSON response is then parsed by <code>toAssetsFromContent<\/code> into a clean format for the UI.<\/p>\n<p>Other commands like <code>rename<\/code>, <code>tag<\/code>, and <code>delete<\/code> follow the same pattern, keeping intent parsing and tool execution separate.<\/p>\n<blockquote>\n<p>See all operation functions on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/lib\/mcp-ops.ts\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<h2>Intelligent Uploads: A File Uploader That Responds<\/h2>\n<p>Handling file uploads in a chat interface requires a smooth flow from the browser to the cloud. Our assistant doesn\u2019t just upload a file; it processes it via the MCP server and immediately responds with the resulting asset, creating an interactive experience.<\/p>\n<p>Let\u2019s follow the journey of a file from the user\u2019s click to the final response.<\/p>\n<h3>1. The Trigger: User Selects a File<\/h3>\n<p>It starts in the <code>ChatInput.tsx<\/code> component. When the user selects a file, the input\u2019s <code>onChange<\/code> event fires, which immediately calls the <code>onSend<\/code> function from the parent container.<\/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\">\/\/ src\/components\/chat\/chat-input.tsx (Snippet)<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleFileChange<\/span>(<span class=\"hljs-params\">event: React.ChangeEvent&lt;HTMLInputElement&gt;<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> file = event.target.files?.&#91;<span class=\"hljs-number\">0<\/span>];\n  <span class=\"hljs-keyword\">if<\/span> (file) {\n    onSend({ file }); <span class=\"hljs-comment\">\/\/ Kicks off the entire upload process<\/span>\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<p>This action packages the <code>File<\/code> object and sends it straight to our Server Action.<\/p>\n<blockquote>\n<p>See the full component on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/components\/chat\/chat-input.tsx\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<h3>2. The Action: Handling the File on the Server<\/h3>\n<p>Our <code>sendMessageAction<\/code> checks for a file before looking for text commands. This gives uploads priority.<\/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\">\/\/ src\/app\/(chat)\/actions.ts (Snippet)<\/span>\n<span class=\"hljs-comment\">\/\/ This block is at the top of our action's logic<\/span>\n<span class=\"hljs-keyword\">if<\/span> (file <span class=\"hljs-keyword\">instanceof<\/span> File) {\n  <span class=\"hljs-comment\">\/\/ Call our dedicated upload operation<\/span>\n  <span class=\"hljs-keyword\">const<\/span> uploaded = <span class=\"hljs-keyword\">await<\/span> uploadFileToFolder(client, file, <span class=\"hljs-string\">\"chat_uploads\"<\/span>);\n\n  <span class=\"hljs-comment\">\/\/ Build a response message containing the new asset<\/span>\n  assistantMsg = {\n    <span class=\"hljs-attr\">id<\/span>: crypto.randomUUID(),\n    <span class=\"hljs-attr\">role<\/span>: <span class=\"hljs-string\">\"assistant\"<\/span>,\n    <span class=\"hljs-attr\">text<\/span>: <span class=\"hljs-string\">\"Image uploaded successfully.\"<\/span>,\n    <span class=\"hljs-attr\">assets<\/span>: uploaded ? &#91;uploaded] : <span class=\"hljs-literal\">undefined<\/span>, <span class=\"hljs-comment\">\/\/ Send asset data back to the UI<\/span>\n  };\n  <span class=\"hljs-keyword\">return<\/span> &#91;...currentState, userMessage, assistantMsg];\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<p>After calling <code>uploadFileToFolder<\/code>, the function returns an <code>assistantMsg<\/code> that includes the uploaded asset. This lets the UI show the result instantly.<\/p>\n<h3>3. The Operation: Sending the File to Cloudinary via MCP<\/h3>\n<p>The last step is in <code>src\/lib\/mcp-ops.ts<\/code>. The <code>uploadFileToFolder<\/code> function prepares the file and calls the correct MCP tool. Files can\u2019t be sent directly as JSON, so we first encode them as <strong>base64 data URIs<\/strong>.<\/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\">\/\/ src\/lib\/mcp-ops.ts (Snippet)<\/span>\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\">uploadFileToFolder<\/span>(<span class=\"hljs-params\">\n  client: MCPClient,\n  file: File\n<\/span>): <span class=\"hljs-title\">Promise<\/span>&lt;<span class=\"hljs-title\">AssetItem<\/span> | <span class=\"hljs-title\">null<\/span>&gt; <\/span>{\n  <span class=\"hljs-comment\">\/\/ 1. Convert the file into a text-based data URI<\/span>\n  <span class=\"hljs-keyword\">const<\/span> buffer = Buffer.from(<span class=\"hljs-keyword\">await<\/span> file.arrayBuffer());\n  <span class=\"hljs-keyword\">const<\/span> dataUri = <span class=\"hljs-string\">`data:<span class=\"hljs-subst\">${file.type}<\/span>;base64,<span class=\"hljs-subst\">${buffer.toString(<span class=\"hljs-string\">\"base64\"<\/span>)}<\/span>`<\/span>;\n\n  <span class=\"hljs-comment\">\/\/ 2. Call the 'upload-asset' tool with the data URI<\/span>\n  <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> client.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>: dataUri,\n        <span class=\"hljs-attr\">fileName<\/span>: file.name,\n        <span class=\"hljs-attr\">folder<\/span>: <span class=\"hljs-string\">\"chat_uploads\"<\/span>,\n      },\n    },\n  });\n\n  <span class=\"hljs-comment\">\/\/ 3. Parse the JSON response from Cloudinary<\/span>\n  <span class=\"hljs-keyword\">return<\/span> parseUploadResult(res?.content) || <span class=\"hljs-literal\">null<\/span>;\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<p><code>client.callTool({ name: 'upload-asset' })<\/code> instructs the MCP gateway to upload the file. The gateway handles Cloudinary communication and returns the new asset\u2019s details, which we parse and send back to the user.<\/p>\n<blockquote>\n<p>See the full upload operation on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/lib\/mcp-ops.ts\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<h2>The AI Brain: Integrating OpenAI for True Natural Language Processing (Optional)<\/h2>\n<p>Our regex-based command handler is fast and effective for specific commands, but it\u2019s rigid. If a user types \u201cshow me my pictures\u201d instead of \u201clist images,\u201d our current logic fails. To make the assistant truly smart, we can integrate an OpenAI model to understand natural language and decide which MCP tool to use.<\/p>\n<p>This transforms the application from a command-line interface into a conversational assistant.<\/p>\n<h3>1. The Limitation of Regex vs. The Power of AI<\/h3>\n<p>The difference lies in <strong>intent detection<\/strong>:<\/p>\n<ul>\n<li>\n<strong>Regex<\/strong> looks for an exact pattern.<\/li>\n<li>\n<strong>AI<\/strong> understands meaning. For example, \u201ccan you get rid of the picture named \u2018test\u2019?\u201d can be mapped to the <code>delete-asset<\/code> tool, something regex cannot reliably do.<\/li>\n<\/ul>\n<h3>2. The AI Router: <code>askOpenAIWithMCP<\/code><\/h3>\n<p>The logic is in <code>src\/lib\/ai-router.ts<\/code>. The idea is to present the entire MCP server to OpenAI as a single tool that the model can use.<\/p>\n<p>The OpenAI SDK supports <code>type: 'mcp'<\/code> for this purpose.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/lib\/ai-router.ts (Snippet)<\/span>\n\n<span class=\"hljs-comment\">\/\/ Describe our MCP server to the OpenAI client<\/span>\n<span class=\"hljs-keyword\">const<\/span> mcpTool = {\n    <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-string\">'mcp'<\/span>,\n    <span class=\"hljs-attr\">server<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-string\">'sse'<\/span>, <span class=\"hljs-attr\">url<\/span>: <span class=\"hljs-string\">'http:\/\/localhost:8787\/sse'<\/span> },\n} <span class=\"hljs-keyword\">as<\/span> ResponsesTool;\n\n<span class=\"hljs-comment\">\/\/ Make the API call<\/span>\n<span class=\"hljs-keyword\">const<\/span> resp = <span class=\"hljs-keyword\">await<\/span> openai.responses.create({\n    <span class=\"hljs-attr\">model<\/span>: <span class=\"hljs-string\">'gpt-4o'<\/span>, <span class=\"hljs-comment\">\/\/ Or your preferred model<\/span>\n    <span class=\"hljs-attr\">input<\/span>: &#91;\n        { <span class=\"hljs-attr\">role<\/span>: <span class=\"hljs-string\">'system'<\/span>, <span class=\"hljs-attr\">content<\/span>: <span class=\"hljs-string\">'You are a Cloudinary asset assistant. Prefer calling MCP tools...'<\/span> },\n        { <span class=\"hljs-attr\">role<\/span>: <span class=\"hljs-string\">'user'<\/span>, <span class=\"hljs-attr\">content<\/span>: userText },\n    ],\n    <span class=\"hljs-attr\">tools<\/span>: &#91;mcpTool],\n    <span class=\"hljs-attr\">tool_choice<\/span>: <span class=\"hljs-string\">'auto'<\/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\">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>What happens here:<\/p>\n<ol>\n<li>Define <code>mcpTool<\/code>, pointing the SDK to the MCP gateway.<\/li>\n<li>Call <code>openai.responses.create<\/code> with a system prompt, the user\u2019s message, and the MCP tool definition.<\/li>\n<li>The model analyzes the text and, if needed, calls the correct MCP tool.<\/li>\n<li>The API response contains both a text reply and any data from the tool call.<\/li>\n<\/ol>\n<p>See the full AI router implementation on GitHub: <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/lib\/ai-router.ts\"><code>src\/lib\/ai-router.ts<\/code><\/a><\/p>\n<h3>3. Activating the AI Brain<\/h3>\n<p>To switch from regex to AI, modify the <code>else<\/code> block in <code>src\/app\/(chat)\/actions.ts<\/code>. Instead of returning a help message, let the AI handle text intent.<\/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\">\/\/ src\/app\/(chat)\/actions.ts (Conceptual Upgrade)<\/span>\n<span class=\"hljs-keyword\">import<\/span> { askOpenAIWithMCP } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/lib\/ai-router\"<\/span>;\n\n<span class=\"hljs-comment\">\/\/ ... inside sendMessageAction ...<\/span>\n\n<span class=\"hljs-keyword\">if<\/span> (file <span class=\"hljs-keyword\">instanceof<\/span> File) {\n  <span class=\"hljs-comment\">\/\/ Keep direct handling for file uploads<\/span>\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n} <span class=\"hljs-keyword\">else<\/span> {\n  <span class=\"hljs-comment\">\/\/ Let the AI router handle ALL text-based intents<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">text<\/span>: replyText, assets } = <span class=\"hljs-keyword\">await<\/span> askOpenAIWithMCP(text);\n  assistantMsg = {\n    <span class=\"hljs-attr\">id<\/span>: crypto.randomUUID(),\n    <span class=\"hljs-attr\">role<\/span>: <span class=\"hljs-string\">\"assistant\"<\/span>,\n    <span class=\"hljs-attr\">text<\/span>: replyText,\n    <span class=\"hljs-attr\">assets<\/span>: assets,\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>With this change, the assistant gains far greater flexibility and intelligence.<\/p>\n<h2>Adding a Human Touch: Using a Secondary AI for Friendlier Chatbots (Optional)<\/h2>\n<p>Functionality is key, but the user experience is what makes an application feel great. Regex-based commands return accurate but robotic responses like \u201c<code>Deleted my-image-id.<\/code>\u201d. We can improve this by adding a second, lightweight AI call whose job is to make the assistant sound more human.<\/p>\n<p>This is a powerful pattern: use one system (regex or a large AI model) to decide <strong>what to do<\/strong>, and a second, faster AI model to decide <strong>how to say it<\/strong>.<\/p>\n<h3>1. The Problem: Static, Hardcoded Responses<\/h3>\n<p>In <code>actions.ts<\/code>, the response text is constructed programmatically.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ A typical response from our regex-based system<\/span>\nassistantMsg = {\n  <span class=\"hljs-comment\">\/\/ ...<\/span>\n  <span class=\"hljs-attr\">text<\/span>: <span class=\"hljs-string\">`Created folder \u201c<span class=\"hljs-subst\">${folderPath}<\/span>\u201d.`<\/span>,\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><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 is clear, but not conversational.<\/p>\n<h3>2. The Solution: An AI-Powered Copywriter<\/h3>\n<p>The <code>src\/lib\/ai-guide.ts<\/code> file introduces a helper function, <code>generateFriendlyReply<\/code>. This function takes the hardcoded default text and uses a fast AI model (like <code>gpt-4o-mini<\/code>) to rewrite it in a warmer, more helpful way.<\/p>\n<p>The improvement comes from carefully designed prompts.<\/p>\n<p><strong>System prompt<\/strong> sets the tone:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/lib\/ai-guide.ts (System Prompt Snippet)<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">buildSystemPrompt<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-string\">\"You are a warm, concise product guide for a Cloudinary MCP chat.\"<\/span>,\n    <span class=\"hljs-string\">\"Tone: friendly, encouraging, not robotic.\"<\/span>,\n    <span class=\"hljs-string\">\"Keep answers short (1\u20133 sentences).\"<\/span>,\n    <span class=\"hljs-string\">\"Never invent features.\"<\/span>,\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n  ].join(<span class=\"hljs-string\">\" \"<\/span>);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><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><strong>User prompt<\/strong> gives context:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/lib\/ai-guide.ts (User Prompt Snippet)<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">buildUserPrompt<\/span>(<span class=\"hljs-params\">input: GuideInput<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-string\">`User said: \"<span class=\"hljs-subst\">${input.userText}<\/span>\"`<\/span>,\n    <span class=\"hljs-string\">`Assistant\u2019s default reply (must keep meaning): \"<span class=\"hljs-subst\">${input.defaultText}<\/span>\"`<\/span>,\n    <span class=\"hljs-string\">\"Rewrite the default reply to be more conversational and helpful...\"<\/span>,\n  ].join(<span class=\"hljs-string\">\"\\n\"<\/span>);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><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 ensures the AI rephrases the default reply without losing accuracy.<\/p>\n<p>See the full prompt design on GitHub: <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\/blob\/main\/src\/lib\/ai-guide.ts\"><code>src\/lib\/ai-guide.ts<\/code><\/a><\/p>\n<h3>3. Integrating the Friendly Replies<\/h3>\n<p>To activate this feature, wrap static text assignments in <code>actions.ts<\/code> with a call to the helper.<\/p>\n<p><strong>Before:<\/strong> Hardcoded response.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/app\/(chat)\/actions.ts (Original)<\/span>\n<span class=\"hljs-keyword\">if<\/span> (createFolderMatch) {\n  <span class=\"hljs-keyword\">const<\/span> folderPath = createFolderMatch&#91;<span class=\"hljs-number\">2<\/span>].trim();\n  <span class=\"hljs-keyword\">const<\/span> ok = <span class=\"hljs-keyword\">await<\/span> createFolderOp(client, folderPath);\n  assistantMsg = {\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n    <span class=\"hljs-attr\">text<\/span>: ok\n      ? <span class=\"hljs-string\">`Created folder \u201c<span class=\"hljs-subst\">${folderPath}<\/span>\u201d.`<\/span>\n      : <span class=\"hljs-string\">`I couldn\u2019t create \u201c<span class=\"hljs-subst\">${folderPath}<\/span>\u201d.`<\/span>,\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><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><strong>After:<\/strong> Conversational response.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ src\/app\/(chat)\/actions.ts (With AI Guide)<\/span>\n<span class=\"hljs-keyword\">import<\/span> { generateFriendlyReply } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/lib\/ai-guide'<\/span>;\n\n<span class=\"hljs-keyword\">if<\/span> (createFolderMatch) {\n  <span class=\"hljs-keyword\">const<\/span> folderPath = createFolderMatch&#91;<span class=\"hljs-number\">2<\/span>].trim();\n  <span class=\"hljs-keyword\">const<\/span> ok = <span class=\"hljs-keyword\">await<\/span> createFolderOp(client, folderPath);\n\n  <span class=\"hljs-keyword\">const<\/span> defaultText = ok ? <span class=\"hljs-string\">`Created folder \u201c<span class=\"hljs-subst\">${folderPath}<\/span>\u201d.`<\/span> : <span class=\"hljs-string\">`I couldn\u2019t create \u201c<span class=\"hljs-subst\">${folderPath}<\/span>\u201d.`<\/span>;\n  <span class=\"hljs-keyword\">const<\/span> friendlyText = <span class=\"hljs-keyword\">await<\/span> generateFriendlyReply({\n      <span class=\"hljs-attr\">userText<\/span>: text,\n      <span class=\"hljs-attr\">defaultText<\/span>: defaultText,\n      <span class=\"hljs-attr\">intent<\/span>: <span class=\"hljs-string\">'create-folder'<\/span>,\n  });\n\n  assistantMsg = { <span class=\"hljs-comment\">\/* ... *\/<\/span>, <span class=\"hljs-attr\">text<\/span>: friendlyText };\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><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>With this change, a message like \u201c<code>Created folder \u201cmarketing\u201d.<\/code>\u201d becomes \u201c<code>All set! The \u201cmarketing\u201d folder has been created for you.<\/code>\u201d This small shift makes the assistant feel much more human.<\/p>\n<h2>Conclusion<\/h2>\n<p>You\u2019ve successfully built a sophisticated AI-powered media assistant. By combining a reactive <strong>Next.js<\/strong> frontend with the powerful tooling of a <strong>Cloudinary MCP Server<\/strong>, you\u2019ve created a conversational interface that can intelligently manage digital assets. This architecture is more than just a proof-of-concept; it\u2019s a blueprint for the future of media management tools.<\/p>\n<p>You now have a robust system that can be extended in many exciting ways.<\/p>\n<h3>Further Reading and Future Enhancements<\/h3>\n<p>Here are a few ideas to take this project to the next level:<\/p>\n<ol>\n<li>\n<strong>Expand the Toolset with More MCPs.<\/strong> The Cloudinary Asset Management MCP is just the beginning. You could integrate other MCPs to unlock new capabilities, such as performing complex transformations or analyzing video content, all from the same chat interface.<\/li>\n<\/ol>\n<ul>\n<li>Resource: <a href=\"https:\/\/cloudinary.com\/documentation\/programmable_media_overview\">Cloudinary Programmable Media Documentation<\/a>\n<\/li>\n<\/ul>\n<ol start=\"2\">\n<li>\n<strong>Integrate AI-powered visual search.<\/strong> Allow users to upload an image and ask, \u201cFind more assets like this.\u201d This would involve creating or using an MCP tool that leverages Cloudinary\u2019s advanced AI features for visual similarity search.<\/li>\n<\/ol>\n<ul>\n<li>Resource: <a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_ai_content_analysis_addon\">Cloudinary AI Content Analysis Features<\/a>\n<\/li>\n<\/ul>\n<ol start=\"3\">\n<li>\n<p><strong>Add user accounts and a database.<\/strong> To turn this into a true multi-tenant service, integrate an authentication provider like NextAuth.js or Clerk. You could then store chat history, user-specific configurations, or API keys in a database like Vercel Postgres or Supabase.<\/p>\n<\/li>\n<li>\n<p><strong>Create custom tools.<\/strong> The Model Context Protocol is an open standard. You can create your own MCP server for any tool you can imagine, from triggering a GitHub Actions workflow to posting a message in Slack, and add it to your AI assistant\u2019s list of capabilities.<\/p>\n<\/li>\n<\/ol>\n<ul>\n<li>Resource: <a href=\"https:\/\/github.com\/modelcontextprotocol\">Model Context Protocol (MCP) on GitHub<\/a>\n<\/li>\n<\/ul>\n<blockquote>\n<p>View the final code on <a href=\"https:\/\/github.com\/musebe\/cloudinary-mcp-media-assistant\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":38947,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[424,336],"class_list":["post-38682","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-agentic","tag-ai"],"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>AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in 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-smart-media-uploader-optimizer-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=\"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in Next.js\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-10-21T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-10-21T16:21:15+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.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-smart-media-uploader-optimizer-mcp-server-next-js#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in Next.js\",\"datePublished\":\"2025-10-21T14:00:00+00:00\",\"dateModified\":\"2025-10-21T16:21:15+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js\"},\"wordCount\":13,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.jpg?_i=AA\",\"keywords\":[\"Agentic\",\"AI\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js\",\"url\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js\",\"name\":\"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.jpg?_i=AA\",\"datePublished\":\"2025-10-21T14:00:00+00:00\",\"dateModified\":\"2025-10-21T16:21:15+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in 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":"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in 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-smart-media-uploader-optimizer-mcp-server-next-js","og_locale":"en_US","og_type":"article","og_title":"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in Next.js","og_url":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js","og_site_name":"Cloudinary Blog","article_published_time":"2025-10-21T14:00:00+00:00","article_modified_time":"2025-10-21T16:21:15+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.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-smart-media-uploader-optimizer-mcp-server-next-js#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in Next.js","datePublished":"2025-10-21T14:00:00+00:00","dateModified":"2025-10-21T16:21:15+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js"},"wordCount":13,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.jpg?_i=AA","keywords":["Agentic","AI"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js","url":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js","name":"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.jpg?_i=AA","datePublished":"2025-10-21T14:00:00+00:00","dateModified":"2025-10-21T16:21:15+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/ai-powered-smart-media-uploader-optimizer-mcp-server-next-js#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"AI-Powered Smart Media Uploader and Optimizer With Cloudinary MCP Server in 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\/v1761063583\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js\/Blog_AI-Powered_Smart_Media_Uploader_Optimizer_with_Cloudinary_MCP_Server_in_Next.js.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38682","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=38682"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38682\/revisions"}],"predecessor-version":[{"id":38683,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38682\/revisions\/38683"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/38947"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=38682"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=38682"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=38682"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}