{"id":38486,"date":"2025-09-08T07:00:00","date_gmt":"2025-09-08T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=38486"},"modified":"2025-09-08T15:23:08","modified_gmt":"2025-09-08T22:23:08","slug":"ai-media-assistant-cloudinary-mcp-server","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server","title":{"rendered":"Building an AI Media Assistant With Cloudinary MCP Server"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Ever wished you could just tell your AI assistant to handle your media files? Not just chat about them, but actually upload, transform, search, and organize your Cloudinary assets through natural conversation? Well, you\u2019re in for a treat!<\/p>\n<p>In this post, we\u2019ll build a fully functional AI media assistant using the <a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_llm_mcp#install_in_cursor\">Cloudinary MCP servers<\/a>. This powerful combination lets your AI seamlessly interact with your Cloudinary media library. By the end of this post, you\u2019ll have a Next.js chat interface where you can interact with AI with natural language to help you manage your assets.<\/p>\n<h2>What is MCP?<\/h2>\n<p>Think of the Model Context Protocol, or MCP, like a universal adapter for AI. You know how a USB-C port lets you plug all sorts of devices into your computer, no matter who made them? MCP does something similar for AI. It\u2019s an open standard that enables powerful AI models, such as Claude, to communicate with external tools and data in a straightforward, standardized manner.<\/p>\n<p>Before MCP, connecting AI to different services was a bit of a headache. You often needed custom code for every single connection. MCP changes that. It provides AI with a clear \u201cinstruction manual\u201d for each tool, making it much easier for your AI assistant to understand what\u2019s available and how to use it.<\/p>\n<p>Here\u2019s why this is game-changing, especially with Cloudinary\u2019s MCP server:<\/p>\n<ul>\n<li>\n<strong>Standardized interface.<\/strong> It\u2019s one protocol to rule them all. Your AI learns one way to interact with tools, making it incredibly flexible.<\/li>\n<li>\n<strong>Dynamic tool discovery.<\/strong> Your AI doesn\u2019t need to be hardcoded with knowledge of every single tool. It can ask the MCP server, \u201cWhat can you do?\u201d and get a list of available actions at runtime.<\/li>\n<li>\n<strong>Type safety.<\/strong> Tools come with built-in schemas that tell the AI exactly what kind of information they need, ensuring proper usage and fewer errors.<\/li>\n<li>\n<strong>Extensibility.<\/strong> Adding new capabilities is a breeze. As Cloudinary introduces new visual media management features through MCP, your assistant can automatically leverage them without requiring you to write new integration code.<\/li>\n<\/ul>\n<p>For Cloudinary users, this means your AI can easily interact with your entire media library using the same robust APIs you already know and love, but all driven through natural language conversations.<\/p>\n<h2>What We\u2019re Building<\/h2>\n<p>Our AI assistant will be a Next.js application that allows users to perform media operations using natural language. The application will interact with a large language model (Claude) that uses the Cloudinary asset management MCP server.<\/p>\n<p>Here is a look at how the final application will look:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1757369323\/blog-Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server-1.png\" alt=\"Cloudinary AI Assistant\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1999\" height=\"1284\"\/><\/p>\n<h2>Prerequisites<\/h2>\n<p>Before we start coding, make sure you have:<\/p>\n<ul>\n<li>\n<strong>Cloudinary account.<\/strong> You\u2019ll need your <strong>cloud name<\/strong>, <strong>API key<\/strong>, and <strong>API secret<\/strong> from your <a href=\"https:\/\/console.cloudinary.com\/app\/home\/dashboard\">Cloudinary dashboard<\/a>.<\/li>\n<li>\n<strong>Anthropic API key.<\/strong> Get one from <a href=\"https:\/\/console.anthropic.com\">console.anthropic.com<\/a> (we\u2019re using Claude for the AI).<\/li>\n<li>\n<strong>Node.js+.<\/strong> The MCP server requires a recent Node version.<\/li>\n<li>\n<strong>npm or yarn.<\/strong> For package management.<\/li>\n<li>\n<strong>Basic TypeScript knowledge.<\/strong> We\u2019ll be using TypeScript throughout.<\/li>\n<\/ul>\n<h2>Step-by-Step Implementation<\/h2>\n<h3>Step 1: Setting Up the Next.js Project<\/h3>\n<p>Let\u2019s quickly create our Next.js project and install the necessary dependencies. We\u2019ll set up a new Next.js project with TypeScript and Tailwind CSS.<\/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> cloudinary-ai-assistant --typescript --tailwind --eslint --app\ncd cloudinary-ai-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\">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>Now, let\u2019s install our key dependencies:<\/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\">npm<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-keyword\">@ai-sdk<\/span>\/anthropic @modelcontextprotocol\/sdk @cloudinary\/asset-management ai zod lucide-react\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>Here\u2019s a quick rundown of the important packages we just installed:<\/p>\n<ul>\n<li>\n<strong><code>@ai-sdk\/anthropic<\/code>.<\/strong> This is the Anthropic model provider from the Vercel AI SDK, making it easy to integrate with Claude, our chosen AI model.<\/li>\n<li>\n<strong><code>@modelcontextprotocol\/sdk<\/code>.<\/strong> This is the official TypeScript SDK for the MCP, an open standard developed by Anthropic to connect AI systems, like large language models (LLMs), with external data sources, tools, and services. We\u2019ll use this to build an MCP client that serves as a bridge between the Cloudinary MCP server and the client.<\/li>\n<li>\n<strong><code>@cloudinary\/asset-management<\/code>.<\/strong> This is Cloudinary\u2019s official MCP server, providing all the powerful media management tools that our AI will use.<\/li>\n<li>\n<strong><code>ai<\/code>.<\/strong> Vercel\u2019s AI SDK, which simplifies streaming AI responses, making our chat feel more responsive.<\/li>\n<\/ul>\n<h3>Step 2: Environment Configuration<\/h3>\n<p>We need to store our Anthropic API key and Cloudinary credentials securely. Create a <code>.env<\/code> file in your project root and add the following:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># Anthropic API Configuration<\/span>\nANTHROPIC_API_KEY=your_anthropic_api_key_here\n\n<span class=\"hljs-comment\"># Cloudinary Configuration<\/span>\nCLOUDINARY_CLOUD_NAME=your_cloud_name_here\nCLOUDINARY_API_KEY=your_api_key_here\nCLOUDINARY_API_SECRET=your_api_secret_here\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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>Step 3: Creating the MCP Client<\/h3>\n<p>We\u2019ll create an MCP client that connects to Cloudinary\u2019s MCP server. This client acts as the bridge, allowing your Next.js application to interact with the powerful Cloudinary media management tools exposed via MCP.<\/p>\n<p>Create a file at <code>src\/lib\/mcp-client.ts<\/code>:<\/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\">\/\/ src\/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> { StdioClientTransport } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@modelcontextprotocol\/sdk\/client\/stdio.js'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">CloudinaryMCPClient<\/span> <\/span>{\n  private client: Client;\n  private transport: StdioClientTransport;\n  private isConnected: boolean = <span class=\"hljs-literal\">false<\/span>;\n\n  <span class=\"hljs-keyword\">constructor<\/span>() {\n    <span class=\"hljs-comment\">\/\/ This part sets up how our application talks to the Cloudinary MCP server.<\/span>\n    <span class=\"hljs-comment\">\/\/ We're using StdioClientTransport to spawn the server as a separate process.<\/span>\n    <span class=\"hljs-keyword\">this<\/span>.transport = <span class=\"hljs-keyword\">new<\/span> StdioClientTransport({\n      <span class=\"hljs-attr\">command<\/span>: process.platform === <span class=\"hljs-string\">'win32'<\/span> ? <span class=\"hljs-string\">'npx.cmd'<\/span> : <span class=\"hljs-string\">'npx'<\/span>,\n      <span class=\"hljs-attr\">args<\/span>: &#91;<span class=\"hljs-string\">'-y'<\/span>, <span class=\"hljs-string\">'--package'<\/span>, <span class=\"hljs-string\">'@cloudinary\/asset-management'<\/span>, <span class=\"hljs-string\">'--'<\/span>, <span class=\"hljs-string\">'mcp'<\/span>, <span class=\"hljs-string\">'start'<\/span>],\n      <span class=\"hljs-attr\">env<\/span>: {\n        <span class=\"hljs-comment\">\/\/ We pass our Cloudinary credentials to the MCP server<\/span>\n        <span class=\"hljs-comment\">\/\/ so it can authenticate and operate on your account.<\/span>\n        <span class=\"hljs-attr\">CLOUDINARY_CLOUD_NAME<\/span>: process.env.CLOUDINARY_CLOUD_NAME || <span class=\"hljs-string\">''<\/span>,\n        <span class=\"hljs-attr\">CLOUDINARY_API_KEY<\/span>: process.env.CLOUDINARY_API_KEY || <span class=\"hljs-string\">''<\/span>,\n        <span class=\"hljs-attr\">CLOUDINARY_API_SECRET<\/span>: process.env.CLOUDINARY_API_SECRET || <span class=\"hljs-string\">''<\/span>,\n        <span class=\"hljs-attr\">PATH<\/span>: process.env.PATH || <span class=\"hljs-string\">''<\/span>, <span class=\"hljs-comment\">\/\/ Ensure PATH is included for npx to find commands<\/span>\n      },\n    });\n\n    <span class=\"hljs-comment\">\/\/ We initialize the MCP client itself.<\/span>\n    <span class=\"hljs-keyword\">this<\/span>.client = <span class=\"hljs-keyword\">new<\/span> Client(\n      {\n        <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'cloudinary-media-assistant'<\/span>,\n        <span class=\"hljs-attr\">version<\/span>: <span class=\"hljs-string\">'1.0.0'<\/span>,\n      },\n      {\n        <span class=\"hljs-attr\">capabilities<\/span>: {\n          <span class=\"hljs-attr\">tools<\/span>: {}, <span class=\"hljs-comment\">\/\/ This will be dynamically populated later<\/span>\n        },\n      }\n    );\n  }\n\n  <span class=\"hljs-comment\">\/\/ Attempts to connect to the MCP server.<\/span>\n  <span class=\"hljs-keyword\">async<\/span> connect(): <span class=\"hljs-built_in\">Promise<\/span>&lt;<span class=\"hljs-keyword\">void<\/span>&gt; {\n    <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.isConnected) <span class=\"hljs-keyword\">return<\/span>; <span class=\"hljs-comment\">\/\/ Avoid reconnecting if already connected<\/span>\n\n    <span class=\"hljs-keyword\">try<\/span> {\n      <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.client.connect(<span class=\"hljs-keyword\">this<\/span>.transport);\n      <span class=\"hljs-keyword\">this<\/span>.isConnected = <span class=\"hljs-literal\">true<\/span>;\n      <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'Connected to Cloudinary MCP server'<\/span>);\n    } <span class=\"hljs-keyword\">catch<\/span> (error) {\n      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Failed to connect to Cloudinary MCP server:'<\/span>, error);\n      <span class=\"hljs-keyword\">throw<\/span> error; <span class=\"hljs-comment\">\/\/ Re-throw to handle connection errors upstream<\/span>\n    }\n  }\n\n  <span class=\"hljs-comment\">\/\/ Lists all the tools (Cloudinary operations) that the MCP server makes available.<\/span>\n  <span class=\"hljs-keyword\">async<\/span> listTools() {\n    <span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-keyword\">this<\/span>.isConnected) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">'Client is not connected to MCP server'<\/span>);\n    }\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.client.listTools();\n  }\n\n  <span class=\"hljs-comment\">\/\/ Calls a specific tool (Cloudinary operation) on the MCP server with given arguments.<\/span>\n  <span class=\"hljs-keyword\">async<\/span> callTool(name: string, <span class=\"hljs-attr\">arguments_<\/span>: Record&lt;string, any&gt;) {\n    <span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-keyword\">this<\/span>.isConnected) {\n      <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">'Client is not connected to MCP server'<\/span>);\n    }\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">this<\/span>.client.callTool({ name, <span class=\"hljs-attr\">arguments<\/span>: arguments_ });\n  }\n\n  <span class=\"hljs-comment\">\/\/ Disconnects from the MCP server, cleaning up resources.<\/span>\n  <span class=\"hljs-keyword\">async<\/span> disconnect(): <span class=\"hljs-built_in\">Promise<\/span>&lt;<span class=\"hljs-keyword\">void<\/span>&gt; {\n    <span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-keyword\">this<\/span>.isConnected) <span class=\"hljs-keyword\">return<\/span>;\n     \n    <span class=\"hljs-keyword\">try<\/span> {\n      <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">this<\/span>.client.close();\n      <span class=\"hljs-keyword\">this<\/span>.isConnected = <span class=\"hljs-literal\">false<\/span>;\n    } <span class=\"hljs-keyword\">catch<\/span> (error) {\n      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Error disconnecting from MCP server:'<\/span>, error);\n    }\n  }\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>The <code>CloudinaryMCPClient<\/code> class serves as our application\u2019s direct connection to the Cloudinary MCP server. It achieves this by using <code>StdioClientTransport<\/code>, which essentially runs the Cloudinary MCP server as a separate child process on our backend. This allows our Next.js app to send commands to it and receive results. The core of starting this server is the <code>npx @cloudinary\/asset-management mcp start<\/code> command, which we pass all our Cloudinary credentials to, allowing the server to authenticate and operate on our Cloudinary account. To optimize performance, the <code>connect()<\/code> and <code>disconnect()<\/code> methods manage the lifecycle of this connection, ensuring we connect only once to avoid overhead. Finally, the <code>listTools()<\/code> and <code>callTool()<\/code> methods are at the heart of MCP interaction. <code>listTools()<\/code> allows our AI to dynamically discover what Cloudinary operations are available (like upload or search), and <code>callTool()<\/code> is then used to execute these operations with specific parameters provided by the AI.<\/p>\n<h3>Step 4: Building the API Route With AI Integration<\/h3>\n<p>Let\u2019s implement the API route. The API route processes user messages, interacts with our AI model, and connects to the Cloudinary MCP tools.<\/p>\n<p>First, create a file at <code>src\/app\/api\/chat\/route.ts<\/code>.<\/p>\n<h4>The Basic AI Chat Endpoint<\/h4>\n<p>We\u2019ll start with a straightforward <code>POST<\/code> API route that takes a user\u2019s message and streams back a response from our AI model (Claude). At this stage, no Cloudinary tools are involved yet.<\/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\">\/\/ src\/app\/api\/chat\/route.ts<\/span>\n<span class=\"hljs-keyword\">import<\/span> { createAnthropic } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@ai-sdk\/anthropic'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { streamText } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'ai'<\/span>; <span class=\"hljs-comment\">\/\/ For streaming AI responses<\/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\">POST<\/span>(<span class=\"hljs-params\">req: Request<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { messages } = <span class=\"hljs-keyword\">await<\/span> req.json();\n\n  <span class=\"hljs-keyword\">const<\/span> anthropicApiKey = process.env.ANTHROPIC_API_KEY;\n  <span class=\"hljs-keyword\">if<\/span> (!anthropicApiKey) {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Response(<span class=\"hljs-string\">'Error: ANTHROPIC_API_KEY environment variable is not set.'<\/span>, { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">500<\/span> });\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> anthropic = createAnthropic({ <span class=\"hljs-attr\">apiKey<\/span>: anthropicApiKey });\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> streamText({\n      <span class=\"hljs-attr\">model<\/span>: anthropic(<span class=\"hljs-string\">'claude-sonnet-3.5-20240620'<\/span>),\n      messages,\n      <span class=\"hljs-attr\">system<\/span>: <span class=\"hljs-string\">`You are a helpful AI assistant. Answer user questions politely.`<\/span>,\n    });\n\n    <span class=\"hljs-keyword\">return<\/span> result.toDataStreamResponse();\n  } <span class=\"hljs-keyword\">catch<\/span> (error: any) {\n    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Chat API error:'<\/span>, error);\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Response(<span class=\"hljs-string\">`Error: <span class=\"hljs-subst\">${error.message}<\/span>`<\/span>, { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">500<\/span> });\n  }\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 code establishes the fundamental structure of our chat API. It\u2019s a standard Next.js API route that listens for <code>POST<\/code> requests containing a history of <code>messages<\/code>. We set up the Anthropic AI model (Claude) and then use the <code>streamText<\/code> function from the <code>ai<\/code> SDK to send the conversation to Claude. The AI processes these messages and streams its textual response back to the client. This initial setup confirms that our AI model is working and ready to handle conversations before we introduce external complexities, such as tool integration.<\/p>\n<h4>Connecting to Cloudinary MCP<\/h4>\n<p>Now, let\u2019s bring the <strong>Cloudinary MCP client<\/strong> into the picture. We\u2019ll connect to the Cloudinary MCP server and dynamically ask it to tell us all the media management operations (tools) it can perform.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" 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\/api\/chat\/route.ts<\/span>\n<span class=\"hljs-comment\">\/\/ ... previous code ...<\/span>\n<span class=\"hljs-keyword\">import<\/span> { CloudinaryMCPClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/lib\/mcp-client'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> mcpClient = <span class=\"hljs-keyword\">new<\/span> CloudinaryMCPClient();\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\">POST<\/span>(<span class=\"hljs-params\">req: Request<\/span>) <\/span>{\n <span class=\"hljs-comment\">\/\/ ... previous code ...<\/span>\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-comment\">\/\/ NEW: Establish connection to the Cloudinary MCP server<\/span>\n    <span class=\"hljs-keyword\">await<\/span> mcpClient.connect();\n\n    <span class=\"hljs-comment\">\/\/ NEW: Ask the MCP server for a list of all the Cloudinary tools it exposes<\/span>\n    <span class=\"hljs-keyword\">const<\/span> mcpToolsResponse = <span class=\"hljs-keyword\">await<\/span> mcpClient.listTools();\n    <span class=\"hljs-keyword\">const<\/span> mcpTools = mcpToolsResponse.tools || &#91;];\n\n    <span class=\"hljs-comment\">\/\/ NEW: Initialize an object to hold AI SDK compatible tools<\/span>\n    <span class=\"hljs-keyword\">const<\/span> aiTools: Record&lt;string, any&gt; = {};\n\n    <span class=\"hljs-comment\">\/\/ NEW: For now, let's just log the discovered tools to verify.<\/span>\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Discovered Cloudinary MCP Tools:\"<\/span>, mcpTools.map(<span class=\"hljs-function\"><span class=\"hljs-params\">t<\/span> =&gt;<\/span> t.name));\n\n    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> streamText({\n      <span class=\"hljs-attr\">model<\/span>: anthropic(<span class=\"hljs-string\">'claude-sonnet-3.5-20240620'<\/span>),\n      messages,\n      <span class=\"hljs-attr\">system<\/span>: <span class=\"hljs-string\">`You are a helpful AI assistant. Answer user questions politely.`<\/span>,\n    });\n\n    <span class=\"hljs-keyword\">return<\/span> result.toDataStreamResponse();\n  } <span class=\"hljs-keyword\">catch<\/span> (error: any) {\n    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Chat API error:'<\/span>, error);\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Response(<span class=\"hljs-string\">`Error: <span class=\"hljs-subst\">${error.message}<\/span>`<\/span>, { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">500<\/span> });\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><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 crucial addition connects our API route to the Cloudinary MCP server through the <code>mcpClient<\/code>. We first call <code>mcpClient.connect()<\/code> to establish communication. Then, <code>mcpClient.listTools()<\/code> is invoked. Instead of us manually defining every Cloudinary operation, the MCP server dynamically provides a list of all the media management tools it exposes (like <code>upload<\/code>, <code>search<\/code>, <code>transform<\/code>). Our AI will eventually use this dynamic list to understand its capabilities. We print them to the console to see what the Cloudinary MCP server has to offer.<\/p>\n<h4>Converting JSON Schemas to Zod Schemas<\/h4>\n<p>Cloudinary MCP tools describe their inputs using standard JSON schemas. For robust type safety and validation when our AI provides arguments for these tools, we convert these JSON schemas into <a href=\"https:\/\/zod.dev\/\">Zod<\/a> schemas. We will create a function that takes an MCP tool and returns a Zod schema.<\/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-keyword\">import<\/span> { z } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'zod'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> convertToZodSchema = (mcpTool: any): z.ZodObject&lt;{}, <span class=\"hljs-string\">\"strip\"<\/span>, z.ZodTypeAny, {}, {}&gt; =&gt; {\n  <span class=\"hljs-keyword\">let<\/span> zodSchema;\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">if<\/span> (mcpTool.inputSchema &amp;&amp; <span class=\"hljs-keyword\">typeof<\/span> mcpTool.inputSchema === <span class=\"hljs-string\">'object'<\/span>) {\n      <span class=\"hljs-comment\">\/\/ Convert JSON Schema to Zod schema<\/span>\n      <span class=\"hljs-keyword\">if<\/span> (mcpTool.inputSchema.type === <span class=\"hljs-string\">'object'<\/span> &amp;&amp; mcpTool.inputSchema.properties) {\n        <span class=\"hljs-keyword\">const<\/span> schemaObj: Record&lt;string, any&gt; = {};\n        <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> &#91;key, prop] <span class=\"hljs-keyword\">of<\/span> <span class=\"hljs-built_in\">Object<\/span>.entries(\n          mcpTool.inputSchema.properties <span class=\"hljs-keyword\">as<\/span> Record&lt;string, any&gt;\n        )) {\n          <span class=\"hljs-keyword\">if<\/span> (prop.type === <span class=\"hljs-string\">'string'<\/span>) {\n            schemaObj&#91;key] = z.string();\n          } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (prop.type === <span class=\"hljs-string\">'number'<\/span>) {\n            schemaObj&#91;key] = z.number();\n          } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (prop.type === <span class=\"hljs-string\">'boolean'<\/span>) {\n            schemaObj&#91;key] = z.boolean();\n          } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (prop.type === <span class=\"hljs-string\">'array'<\/span>) {\n            schemaObj&#91;key] = z.array(z.any());\n          } <span class=\"hljs-keyword\">else<\/span> {\n            schemaObj&#91;key] = z.any();\n          }\n\n          <span class=\"hljs-comment\">\/\/ Make optional if not required<\/span>\n          <span class=\"hljs-keyword\">const<\/span> required = mcpTool.inputSchema.required <span class=\"hljs-keyword\">as<\/span> string&#91;] | <span class=\"hljs-literal\">undefined<\/span>;\n          <span class=\"hljs-keyword\">if<\/span> (!required?.includes(key)) {\n            schemaObj&#91;key] = schemaObj&#91;key].optional();\n          }\n        }\n        zodSchema = z.object(schemaObj);\n      } <span class=\"hljs-keyword\">else<\/span> {\n        zodSchema = z.object({});\n      }\n    } <span class=\"hljs-keyword\">else<\/span> {\n      zodSchema = z.object({});\n    }\n  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n    <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">`Failed to parse schema for tool <span class=\"hljs-subst\">${mcpTool.name}<\/span>:`<\/span>, error);\n    zodSchema = z.object({});\n  }\n  <span class=\"hljs-keyword\">return<\/span> zodSchema;\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 code iterates through each <code>mcpTool<\/code> that the Cloudinary MCP server exposes. Each of these tools comes with an <code>inputSchema<\/code>, which is a standard JSON Schema describing what arguments the tool expects. The Vercel AI SDK, which we\u2019re using to integrate with Claude, works best with Zod schemas for defining tool parameters. This section of code dynamically converts the JSON Schema into a corresponding Zod schema. This is crucial for type safety and helps the AI model correctly understand and validate the inputs it needs to provide when calling a Cloudinary tool, preventing errors and ensuring smooth operation.<\/p>\n<h4>Defining AI SDK Tools and Executing Cloudinary Operations<\/h4>\n<p>With the Zod schemas in hand, we can now formally define each Cloudinary MCP tool in a way the Vercel AI SDK understands. This includes telling the AI what the tool does (its description) and, most importantly, how to execute it by calling our <code>mcpClient.callTool()<\/code>.<\/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\">\/\/ src\/app\/api\/chat\/route.ts<\/span>\n<span class=\"hljs-comment\">\/\/ ...Previous code ...<\/span>\n<span class=\"hljs-keyword\">import<\/span> { convertToZodSchema } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@\/lib\/convert-to-zod-schema'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> mcpClient = <span class=\"hljs-keyword\">new<\/span> CloudinaryMCPClient();\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\">POST<\/span>(<span class=\"hljs-params\">req: Request<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ ...Previous code ...<\/span>\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">await<\/span> mcpClient.connect();\n    <span class=\"hljs-keyword\">const<\/span> mcpToolsResponse = <span class=\"hljs-keyword\">await<\/span> mcpClient.listTools();\n    <span class=\"hljs-keyword\">const<\/span> mcpTools = mcpToolsResponse.tools || &#91;];\n    <span class=\"hljs-keyword\">const<\/span> aiTools: Record&lt;string, any&gt; = {};\n\n    <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> mcpTool <span class=\"hljs-keyword\">of<\/span> mcpTools) {\n     <span class=\"hljs-keyword\">const<\/span> zodSchema = convertToZodSchema(mcpTool);\n      <span class=\"hljs-comment\">\/\/ Create an AI SDK 'tool' definition for each Cloudinary MCP tool.<\/span>\n      aiTools&#91;mcpTool.name] = tool({\n        <span class=\"hljs-attr\">description<\/span>: mcpTool.description, <span class=\"hljs-comment\">\/\/ AI uses this to understand the tool's purpose<\/span>\n        <span class=\"hljs-attr\">parameters<\/span>: zodSchema,          <span class=\"hljs-comment\">\/\/ AI uses this Zod schema to understand required inputs<\/span>\n        <span class=\"hljs-attr\">execute<\/span>: <span class=\"hljs-keyword\">async<\/span> (args: any) =&gt; { <span class=\"hljs-comment\">\/\/ This function runs when the AI decides to use the tool<\/span>\n          <span class=\"hljs-keyword\">try<\/span> {\n            <span class=\"hljs-comment\">\/\/ Call our CloudinaryMCPClient to execute the actual Cloudinary operation.<\/span>\n            <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> mcpClient.callTool(mcpTool.name, args);\n            <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Executed tool <span class=\"hljs-subst\">${mcpTool.name}<\/span> with args:`<\/span>, args);\n            <span class=\"hljs-keyword\">return<\/span> result.content; <span class=\"hljs-comment\">\/\/ Return the result back to the AI for its response<\/span>\n          } <span class=\"hljs-keyword\">catch<\/span> (error) {\n            <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">`Error executing tool <span class=\"hljs-subst\">${mcpTool.name}<\/span>:`<\/span>, error);\n            <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">error<\/span>: <span class=\"hljs-string\">`Failed to execute <span class=\"hljs-subst\">${mcpTool.name}<\/span>: <span class=\"hljs-subst\">${error}<\/span>`<\/span> };\n          }\n        },\n      });\n    } <span class=\"hljs-comment\">\/\/ End of the for loop<\/span>\n\n    <span class=\"hljs-comment\">\/\/ ... rest of the code ...<\/span>\n  } <span class=\"hljs-keyword\">catch<\/span> (error: any) {\n    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Chat API error:'<\/span>, error);\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Response(<span class=\"hljs-string\">`Error: <span class=\"hljs-subst\">${error.message}<\/span>`<\/span>, { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">500<\/span> });\n  }\n}\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<p>For each Cloudinary tool discovered, we\u2019ll construct an <strong>AI SDK <code>tool<\/code> object<\/strong>.<\/p>\n<ul>\n<li>The <code>description<\/code> is crucial because it helps the AI model decide when to use a particular tool based on the user\u2019s natural language.<\/li>\n<li>The <code>parameters<\/code> property (which uses our previously generated Zod schema) guides the AI on what arguments to provide for the tool.<\/li>\n<li>The <code>execute<\/code> function is the core of the tool\u2019s functionality. When the AI determines it needs to perform a Cloudinary operation (e.g., in response to \u201cupload this image\u201d), this asynchronous function is triggered. Inside, it simply calls our <code>mcpClient.callTool()<\/code>, passing the tool\u2019s original name and the arguments that the AI has intelligently extracted from the user\u2019s query. The result from Cloudinary\u2019s MCP server is then returned to the AI to inform its next conversational turn.<\/li>\n<\/ul>\n<h4>Integrating Tools into the AI Model and Adding a System Prompt<\/h4>\n<p>Now that our Cloudinary tools are defined, we\u2019ll pass them to the <strong>AI model<\/strong>. We\u2019ll also refine the AI\u2019s <strong>system prompt<\/strong> to instruct it on its role as a Cloudinary assistant and inform it about the newly available tools.<\/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\/app\/api\/chat\/route.ts<\/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\">POST<\/span>(<span class=\"hljs-params\">req: Request<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { messages } = <span class=\"hljs-keyword\">await<\/span> req.json();\n  <span class=\"hljs-keyword\">const<\/span> anthropicApiKey = process.env.ANTHROPIC_API_KEY;\n  <span class=\"hljs-keyword\">if<\/span> (!anthropicApiKey) { <span class=\"hljs-comment\">\/* ... handle error ... *\/<\/span> }\n  <span class=\"hljs-keyword\">const<\/span> anthropic = createAnthropic({ <span class=\"hljs-attr\">apiKey<\/span>: anthropicApiKey });\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">await<\/span> mcpClient.connect();\n    <span class=\"hljs-keyword\">const<\/span> mcpToolsResponse = <span class=\"hljs-keyword\">await<\/span> mcpClient.listTools();\n    <span class=\"hljs-keyword\">const<\/span> mcpTools = mcpToolsResponse.tools || &#91;];\n    <span class=\"hljs-keyword\">const<\/span> aiTools: Record&lt;string, any&gt; = {};\n\n    <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> mcpTool <span class=\"hljs-keyword\">of<\/span> mcpTools) {\n      <span class=\"hljs-keyword\">let<\/span> zodSchema;\n      <span class=\"hljs-comment\">\/\/ ... previous Zod schema conversion logic here ...<\/span>\n      aiTools&#91;mcpTool.name] = tool({ <span class=\"hljs-comment\">\/* ... *\/<\/span> });\n    }\n\n    <span class=\"hljs-comment\">\/\/ MODIFIED: Stream the AI's response, now with 'tools' and an updated 'system' prompt<\/span>\n    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> streamText({\n      <span class=\"hljs-attr\">model<\/span>: anthropic(<span class=\"hljs-string\">'claude-sonnet-3.5-20240620'<\/span>),\n      messages,\n      <span class=\"hljs-attr\">tools<\/span>: aiTools, <span class=\"hljs-comment\">\/\/ NEW: Pass the Cloudinary tools to the AI!<\/span>\n      <span class=\"hljs-attr\">maxSteps<\/span>: <span class=\"hljs-number\">5<\/span>, <span class=\"hljs-comment\">\/\/ Limit tool calls to prevent potential infinite loops<\/span>\n      <span class=\"hljs-attr\">system<\/span>: <span class=\"hljs-string\">`You are a helpful AI assistant that can manage Cloudinary assets.\n        \nAvailable tools: <span class=\"hljs-subst\">${mcpTools <span class=\"hljs-regexp\">\/\/<\/span> NEW: Clearly list tools and their descriptions <span class=\"hljs-keyword\">for<\/span> the AI\n        .map((t) =&gt; <span class=\"hljs-string\">`- <span class=\"hljs-subst\">${t.name}<\/span>: <span class=\"hljs-subst\">${t.description}<\/span>`<\/span>)\n        .join(<span class=\"hljs-string\">'\\n'<\/span>)}<\/span>\n\nWhen users ask you to perform Cloudinary operations, use the appropriate tools to help them.\nBe conversational and explain what you're doing.`<\/span>,\n    });\n\n    <span class=\"hljs-keyword\">return<\/span> result.toDataStreamResponse();\n  } <span class=\"hljs-keyword\">catch<\/span> (error: any) {\n    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">'Chat API error:'<\/span>, error);\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Response(<span class=\"hljs-string\">`Error: <span class=\"hljs-subst\">${error.message}<\/span>`<\/span>, { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">500<\/span> });\n  }\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 is where all the pieces come together. The <code>tools: aiTools<\/code> parameter, passed to <code>streamText<\/code>, makes all our defined Cloudinary tools directly available to the AI model. The AI can now intelligently decide when to invoke a specific Cloudinary operation based on the user\u2019s request. The <code>system<\/code> prompt is vital because it acts as the AI\u2019s instruction manual. We explicitly tell Claude its role (\u201ca helpful AI assistant that can manage Cloudinary assets\u201d) and provide a dynamic list of all the tools it can use, along with their descriptions. This guides the AI to correctly interpret user intent and choose the right Cloudinary operation, ultimately allowing it to perform powerful media management tasks through natural conversation.<\/p>\n<h3>Step 5: Creating the Chat Interface<\/h3>\n<p>Now that we have the server connected to the AI model, let\u2019s add the chat interface that allows users to interact with our AI assistant. This component will handle displaying messages, capturing user input, and sending it to our API route. Since we are not focusing on the UI part of the app, we will paste the full UI implementation.<\/p>\n<p>Open <code>src\/app\/page.tsx<\/code> and ensure it has the following content:<\/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\">\/\/ src\/app\/page.tsx<\/span>\n<span class=\"hljs-string\">'use client'<\/span>; <span class=\"hljs-comment\">\/\/ This directive makes the component a client component in Next.js<\/span>\n\n<span class=\"hljs-keyword\">import<\/span> { useChat } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@ai-sdk\/react'<\/span>; <span class=\"hljs-comment\">\/\/ Hook from Vercel's AI SDK for chat management<\/span>\n<span class=\"hljs-keyword\">import<\/span> { UIMessage } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'ai'<\/span>; <span class=\"hljs-comment\">\/\/ Type definition for AI SDK messages<\/span>\n<span class=\"hljs-keyword\">import<\/span> { Send, Bot, User } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'lucide-react'<\/span>; <span class=\"hljs-comment\">\/\/ Icons for the UI<\/span>\n\n<span class=\"hljs-comment\">\/\/ Helper function to find and extract tool invocation parts from a message.<\/span>\n<span class=\"hljs-comment\">\/\/ This is used to show the user when the AI has called a Cloudinary tool.<\/span>\n<span class=\"hljs-keyword\">const<\/span> getInvocationPartsFromMessage = <span class=\"hljs-function\">(<span class=\"hljs-params\">message: UIMessage<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> message.parts?.filter(<span class=\"hljs-function\">(<span class=\"hljs-params\">part<\/span>) =&gt;<\/span> part.type === <span class=\"hljs-string\">'tool-invocation'<\/span>) || &#91;];\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\">ChatPage<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ useChat hook manages our chat state: messages, input, and handlers.<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();\n\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\">\"max-w-4xl mx-auto h-&#91;calc(100vh-200px)] flex flex-col p-4\"<\/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\">\"mb-6\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-3xl font-bold\"<\/span>&gt;<\/span>Cloudinary AI Assistant<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-muted-foreground\"<\/span>&gt;<\/span>\n          Ask me to help you manage your Cloudinary assets. I can upload, search,\n          transform, and organize your media files.\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n      {\/* This div displays the chat messages and handles scrolling *\/}\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex-1 overflow-y-auto space-y-4 mb-4 p-4 border rounded-lg bg-muted\/20\"<\/span>&gt;<\/span>\n        {messages.length === 0 ? (\n          \/\/ Display a welcome message and example prompts if no messages exist yet.\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-center text-muted-foreground py-8\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Bot<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-auto h-12 w-12 mb-4\"<\/span> \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h3<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-lg font-semibold mb-2\"<\/span>&gt;<\/span>Welcome to Cloudinary AI Assistant<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h3<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mb-4\"<\/span>&gt;<\/span>Try asking me something like:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/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\">\"space-y-2 text-sm\"<\/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\">\"bg-background p-3 rounded border\"<\/span>&gt;<\/span>\n                \"Upload this image to Cloudinary, tag it as 'profile' and store it in the users folder\"\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\">\"bg-background p-3 rounded border\"<\/span>&gt;<\/span>\n                \"Search for all images tagged with 'product' in the catalog folder\"\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          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        ) : (\n          \/\/ Map through messages and display them.\n          messages.map((message) =&gt; (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>\n              <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{message.id}<\/span>\n              \/\/ <span class=\"hljs-attr\">Adjust<\/span> <span class=\"hljs-attr\">alignment<\/span> <span class=\"hljs-attr\">based<\/span> <span class=\"hljs-attr\">on<\/span> <span class=\"hljs-attr\">who<\/span> <span class=\"hljs-attr\">sent<\/span> <span class=\"hljs-attr\">the<\/span> <span class=\"hljs-attr\">message<\/span> (<span class=\"hljs-attr\">user<\/span> <span class=\"hljs-attr\">or<\/span> <span class=\"hljs-attr\">bot<\/span>)<span class=\"hljs-attr\">.<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{clsx(<\/span>\"<span class=\"hljs-attr\">flex<\/span> <span class=\"hljs-attr\">gap-3<\/span>\", {\n                \"<span class=\"hljs-attr\">justify-end<\/span>\"<span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">message.role<\/span> === <span class=\"hljs-string\">'user'<\/span>,\n                \"<span class=\"hljs-attr\">justify-start<\/span>\"<span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">message.role<\/span> !== <span class=\"hljs-string\">'user'<\/span>,\n              })}\n            &gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{clsx(<\/span>\"<span class=\"hljs-attr\">flex<\/span> <span class=\"hljs-attr\">gap-3<\/span> <span class=\"hljs-attr\">max-w-<\/span>&#91;<span class=\"hljs-attr\">80<\/span>%]\", {\n                \"<span class=\"hljs-attr\">flex-row-reverse<\/span>\"<span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">message.role<\/span> === <span class=\"hljs-string\">'user'<\/span>, \/\/ <span class=\"hljs-attr\">Reverse<\/span> <span class=\"hljs-attr\">order<\/span> <span class=\"hljs-attr\">for<\/span> <span class=\"hljs-attr\">user<\/span> <span class=\"hljs-attr\">message<\/span> (<span class=\"hljs-attr\">icon<\/span> <span class=\"hljs-attr\">on<\/span> <span class=\"hljs-attr\">right<\/span>)\n                \"<span class=\"hljs-attr\">flex-row<\/span>\"<span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">message.role<\/span> !== <span class=\"hljs-string\">'user'<\/span>,        \/\/ <span class=\"hljs-attr\">Normal<\/span> <span class=\"hljs-attr\">order<\/span> <span class=\"hljs-attr\">for<\/span> <span class=\"hljs-attr\">bot<\/span> <span class=\"hljs-attr\">message<\/span> (<span class=\"hljs-attr\">icon<\/span> <span class=\"hljs-attr\">on<\/span> <span class=\"hljs-attr\">left<\/span>)\n              })}&gt;<\/span>\n                {\/* User or Bot Avatar *\/}\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{clsx(<\/span>\"<span class=\"hljs-attr\">flex-shrink-0<\/span> <span class=\"hljs-attr\">w-8<\/span> <span class=\"hljs-attr\">h-8<\/span> <span class=\"hljs-attr\">rounded-full<\/span> <span class=\"hljs-attr\">flex<\/span> <span class=\"hljs-attr\">items-center<\/span> <span class=\"hljs-attr\">justify-center<\/span>\", {\n                  \"<span class=\"hljs-attr\">bg-primary<\/span> <span class=\"hljs-attr\">text-primary-foreground<\/span>\"<span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">message.role<\/span> === <span class=\"hljs-string\">'user'<\/span>,\n                  \"<span class=\"hljs-attr\">bg-muted<\/span> <span class=\"hljs-attr\">text-muted-foreground<\/span>\"<span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">message.role<\/span> !== <span class=\"hljs-string\">'user'<\/span>,\n                })}&gt;<\/span>\n                  {message.role === 'user' ? <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">User<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-4 w-4\"<\/span> \/&gt;<\/span> : <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Bot<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-4 w-4\"<\/span> \/&gt;<\/span>}\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                {\/* Message Bubble *\/}\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{clsx(<\/span>\"<span class=\"hljs-attr\">p-3<\/span> <span class=\"hljs-attr\">rounded-lg<\/span>\", {\n                  \"<span class=\"hljs-attr\">bg-primary<\/span> <span class=\"hljs-attr\">text-primary-foreground<\/span>\"<span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">message.role<\/span> === <span class=\"hljs-string\">'user'<\/span>, \/\/ <span class=\"hljs-attr\">Primary<\/span> <span class=\"hljs-attr\">color<\/span> <span class=\"hljs-attr\">for<\/span> <span class=\"hljs-attr\">user<\/span> <span class=\"hljs-attr\">messages<\/span>\n                  \"<span class=\"hljs-attr\">bg-background<\/span> <span class=\"hljs-attr\">border<\/span>\"<span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">message.role<\/span> !== <span class=\"hljs-string\">'user'<\/span>,            \/\/ <span class=\"hljs-attr\">Background<\/span> <span class=\"hljs-attr\">color<\/span> <span class=\"hljs-attr\">with<\/span> <span class=\"hljs-attr\">border<\/span> <span class=\"hljs-attr\">for<\/span> <span class=\"hljs-attr\">bot<\/span> <span class=\"hljs-attr\">messages<\/span>\n                })}&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"whitespace-pre-wrap\"<\/span>&gt;<\/span>{message.content}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                  {\/* If the message involves a tool call, display it for transparency. *\/}\n                  {getInvocationPartsFromMessage(message).length &gt; 0 &amp;&amp; (\n                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-2 pt-2 border-t border-current\/20\"<\/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\">\"text-xs opacity-70 mb-1\"<\/span>&gt;<\/span>Tool calls:<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                      {getInvocationPartsFromMessage(message).map((tool, index) =&gt; (\n                        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{index}<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-xs bg-current\/10 p-2 rounded mb-1\"<\/span>&gt;<\/span>\n                          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">strong<\/span>&gt;<\/span>{tool.toolInvocation.toolName}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">strong<\/span>&gt;<\/span>\n                        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                      ))}\n                    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/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\">div<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          ))\n        )}\n\n        {\/* Loading Indicator: Shows \"Thinking...\" when AI is processing *\/}\n        {isLoading &amp;&amp; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex gap-3 justify-start\"<\/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\">\"flex gap-3 max-w-&#91;80%]\"<\/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\">\"flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center bg-muted text-muted-foreground\"<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Bot<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-4 w-4\"<\/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 rounded-lg bg-background border\"<\/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\">\"flex items-center gap-2\"<\/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\">\"animate-spin rounded-full h-4 w-4 border-b-2 border-primary\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-muted-foreground\"<\/span>&gt;<\/span>Thinking...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/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>&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>&gt;<\/span>\n        )}\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n      {\/* Input form for user to type messages *\/}\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"space-y-3\"<\/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\">\"flex gap-2\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n            <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{input}<\/span>\n            <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{handleInputChange}<\/span>\n            <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Ask me to help with your Cloudinary assets...\"<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex-1 input\"<\/span> \/\/ <span class=\"hljs-attr\">Uses<\/span> <span class=\"hljs-attr\">a<\/span> <span class=\"hljs-attr\">custom<\/span> <span class=\"hljs-attr\">Tailwind<\/span> <span class=\"hljs-attr\">style<\/span> <span class=\"hljs-attr\">defined<\/span> <span class=\"hljs-attr\">in<\/span> <span class=\"hljs-attr\">globals.css<\/span>\n            <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{isLoading}<\/span> \/\/ <span class=\"hljs-attr\">Disable<\/span> <span class=\"hljs-attr\">input<\/span> <span class=\"hljs-attr\">while<\/span> <span class=\"hljs-attr\">AI<\/span> <span class=\"hljs-attr\">is<\/span> <span class=\"hljs-attr\">thinking<\/span>\n          \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n            <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span>\n            <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{isLoading<\/span> || !<span class=\"hljs-attr\">input.trim<\/span>()} \/\/ <span class=\"hljs-attr\">Disable<\/span> <span class=\"hljs-attr\">button<\/span> <span class=\"hljs-attr\">if<\/span> <span class=\"hljs-attr\">loading<\/span> <span class=\"hljs-attr\">or<\/span> <span class=\"hljs-attr\">input<\/span> <span class=\"hljs-attr\">is<\/span> <span class=\"hljs-attr\">empty<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"btn btn-primary px-4\"<\/span> \/\/ <span class=\"hljs-attr\">Uses<\/span> <span class=\"hljs-attr\">a<\/span> <span class=\"hljs-attr\">custom<\/span> <span class=\"hljs-attr\">Tailwind<\/span> <span class=\"hljs-attr\">style<\/span> <span class=\"hljs-attr\">defined<\/span> <span class=\"hljs-attr\">in<\/span> <span class=\"hljs-attr\">globals.css<\/span>\n          &gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Send<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-4 w-4\"<\/span> \/&gt;<\/span> {\/* Send icon *\/}\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/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\">form<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\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<h2>Testing and Usage<\/h2>\n<p>Now, let\u2019s fire up our application and see your Cloudinary AI Assistant in action!<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm run dev\n<\/code><\/span><\/pre>\n<p>Navigate to <code>http:\/\/localhost:3000<\/code> in your browser, and you should see your Cloudinary AI Assistant looking like below:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1757369323\/blog-Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server-1.png\" alt=\"Cloudinary AI Assistant\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1999\" height=\"1284\"\/><\/p>\n<h3>Example Conversations<\/h3>\n<p><strong>Upload an Image:<\/strong><\/p>\n<p>I prompted the AI assistant to upload an image by passing the path to the image in my system, and here is a screenshot of the result:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1757369323\/blog-Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server-2.png\" alt=\"Cloudinary AI Assistant prompt: Here is the path to an image. Upload this image to cloudinary, tag it as \u2018pets\u2019 and store it in the mcp-demo folder.\u201c\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1374\" height=\"1480\"\/>\n<strong>Search for Assets:<\/strong>\nI prompted the AI assistant to search for all assets uploaded in the current month and return a breakdown of assets uploaded per day, and here is a screenshot of the result:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1757369323\/blog-Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server-3.png\" alt=\"Cloudinary AI Assistant replies\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1168\" height=\"1638\"\/><\/p>\n<p><strong>Performing other tasks unrelated to Cloudinary media management:<\/strong>\nI asked the AI assistant to tell me the weather in San Francisco, and it replied that it can only handle Cloudinary media management operations. This is possible thanks to the system prompt we provided earlier.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1757369323\/blog-Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server-4.png\" alt=\"Cloudinary AI Assistant prompt: What\u2019s the weather like in San Francisco right now? The Assistant replies that it doesn\u2019t have access to weather information tools and is specifically designed to help you manage Cloudinary assets.\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1999\" height=\"1308\"\/><\/p>\n<h2>Key Insights and Technical Highlights<\/h2>\n<p>Building this assistant truly highlighted the immense power and simplicity of working with Cloudinary\u2019s MCP server. It makes handling complex media management straightforward with natural language commands. For instance, you can simply tell your AI assistant, \u201cHere\u2019s the public ID of an image in Cloudinary, apply a blur filter transformation on the image, and return the URL.\u201d The AI assistant then handles this request by calling the right tools and formatting the response into a readable, easy-to-understand text. This capability comes down to a few key technical advantages.<\/p>\n<p>Firstly, there\u2019s true dynamic tool discovery. Cloudinary\u2019s MCP server tells your AI, in real-time, exactly what actions it can perform. If the Cloudinary MCP server introduces a brand new tool, your assistant can discover and use it automatically, with no need for new integration code.<\/p>\n<p>Secondly, you get built-in type safety. Cloudinary\u2019s MCP tools use well-defined structures that ensure your AI always uses features correctly and safely, preventing errors and making the integration incredibly reliable.<\/p>\n<p>Finally, this setup ensures a Clean Separation of Concerns. Your AI remains focused on understanding what users want and responding intelligently, while Cloudinary\u2019s MCP server expertly handles all the complex interactions with its powerful media management APIs. This clear division makes your codebase much cleaner, easier to maintain, and ready to scale.<\/p>\n<h2>Troubleshooting Common Issues<\/h2>\n<h3>MCP Server Won\u2019t Start<\/h3>\n<p>If you see errors about the MCP server not starting, check:<\/p>\n<ol>\n<li>\n<strong>Node.js version.<\/strong> Confirm you have Node.js 18+ installed by running <code>node -v<\/code>. The MCP server requires a recent version.<\/li>\n<li>\n<strong>Package installation.<\/strong> Ensure that <code>@cloudinary\/asset-management<\/code> is correctly installed in your <code>node_modules<\/code>. Run <code>npm install<\/code> again to be safe.<\/li>\n<li>\n<strong>Cloudinary credentials.<\/strong> Double-check your <code>.env<\/code> file. Are your <code>CLOUDINARY_CLOUD_NAME<\/code>, <code>CLOUDINARY_API_KEY<\/code>, and <code>CLOUDINARY_API_SECRET<\/code> accurately copied from your <a href=\"https:\/\/console.cloudinary.com\/app\/home\/dashboard\">Cloudinary dashboard<\/a>? Even a small typo can prevent authentication.<\/li>\n<li>\n<strong>Windows specific.<\/strong> If you\u2019re on Windows, make sure <code>npx.cmd<\/code> is correctly configured in your system\u2019s PATH.<\/li>\n<\/ol>\n<h3>AI Not Using Tools<\/h3>\n<p>If the AI isn\u2019t calling Cloudinary tools even when it should, and just responds with text:<\/p>\n<ol>\n<li>\n<strong>System prompt.<\/strong> Review the <code>system<\/code> prompt in <code>src\/app\/api\/chat\/route.ts<\/code>. This is how the AI learns about its capabilities. Does it clearly explain the AI\u2019s role and list all the available tools, along with their descriptions?<\/li>\n<li>\n<strong>Tool discovery.<\/strong> Check your server\u2019s console logs to confirm that the <code>mcpClient.listTools()<\/code> call is successfully returning the expected Cloudinary tools. If the AI doesn\u2019t know about the tools, it can\u2019t use them!<\/li>\n<li>\n<strong>Prompt specificity.<\/strong> Make your conversational prompts to the AI specific enough. For example, \u201cupload this image\u201d is much clearer than just \u201chandle this image.\u201d The AI needs enough context to decide which tool is appropriate.<\/li>\n<\/ol>\n<h2>Conclusion<\/h2>\n<p>The MCP represents a fundamental shift in how we think about AI integrations. Rather than rigid, one-off connections, MCP creates a standardized, dynamic ecosystem where tools can be discovered, understood, and used intelligently by AI models.<\/p>\n<p>Cloudinary\u2019s MCP server is a powerful innovation for media management. It enables AI models to execute Cloudinary operations, such as uploading, searching, and transforming assets using natural language. This makes your Cloudinary media infrastructure intelligently responsive to AI commands.<\/p>\n<p>In this post, we built an AI assistant powered by the Cloudinary MCP server, demonstrating how it can handle media management tasks through simple conversation. This approach simplifies workflows for marketers, content creators, or developers to manage Cloudinary assets by simply telling the AI what they need.\n<a href=\"https:\/\/cloudinary.com\/contact\">Contact us today to learn more<\/a> about how Cloudinary can help simplify your content development workflows.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":38487,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[424],"class_list":["post-38486","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-agentic"],"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 Media Assistant With Cloudinary MCP Server<\/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-media-assistant-cloudinary-mcp-server\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building an AI Media Assistant With Cloudinary MCP Server\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-09-08T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-09-08T22:23:08+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.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-media-assistant-cloudinary-mcp-server#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Building an AI Media Assistant With Cloudinary MCP Server\",\"datePublished\":\"2025-09-08T14:00:00+00:00\",\"dateModified\":\"2025-09-08T22:23:08+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server\"},\"wordCount\":9,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.jpg?_i=AA\",\"keywords\":[\"Agentic\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server\",\"url\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server\",\"name\":\"Building an AI Media Assistant With Cloudinary MCP Server\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.jpg?_i=AA\",\"datePublished\":\"2025-09-08T14:00:00+00:00\",\"dateModified\":\"2025-09-08T22:23:08+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building an AI Media Assistant With Cloudinary MCP Server\"}]},{\"@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 Media Assistant With Cloudinary MCP Server","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-media-assistant-cloudinary-mcp-server","og_locale":"en_US","og_type":"article","og_title":"Building an AI Media Assistant With Cloudinary MCP Server","og_url":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server","og_site_name":"Cloudinary Blog","article_published_time":"2025-09-08T14:00:00+00:00","article_modified_time":"2025-09-08T22:23:08+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.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-media-assistant-cloudinary-mcp-server#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Building an AI Media Assistant With Cloudinary MCP Server","datePublished":"2025-09-08T14:00:00+00:00","dateModified":"2025-09-08T22:23:08+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server"},"wordCount":9,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.jpg?_i=AA","keywords":["Agentic"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server","url":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server","name":"Building an AI Media Assistant With Cloudinary MCP Server","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.jpg?_i=AA","datePublished":"2025-09-08T14:00:00+00:00","dateModified":"2025-09-08T22:23:08+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/ai-media-assistant-cloudinary-mcp-server#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building an AI Media Assistant With Cloudinary MCP Server"}]},{"@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\/v1757024449\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server\/Blog_Building_an_AI_Media_Assistant_with_Cloudinary_MCP_Server.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38486","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=38486"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38486\/revisions"}],"predecessor-version":[{"id":38488,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38486\/revisions\/38488"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/38487"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=38486"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=38486"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=38486"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}