{"id":898,"date":"2026-03-29T18:26:53","date_gmt":"2026-03-29T18:26:53","guid":{"rendered":"https:\/\/pixelpanda.ai\/blog\/2026\/03\/29\/how-to-build-mcp-server-ai-image-processing\/"},"modified":"2026-04-08T03:01:54","modified_gmt":"2026-04-08T03:01:54","slug":"how-to-build-mcp-server-ai-image-processing","status":"publish","type":"post","link":"https:\/\/pixelpanda.ai\/blog\/2026\/03\/29\/how-to-build-mcp-server-ai-image-processing\/","title":{"rendered":"How to Build an MCP Server for AI Image Processing with PixelPanda"},"content":{"rendered":"<p>You know the ritual. Open Photoshop. Export. Upload to your product photography tool. Wait. Download. Re-upload somewhere else. Rename files. Repeat forty-seven times before lunch.<\/p>\n<p>What if you could just <em>say<\/em> what you need?<\/p>\n<p>&#8220;Remove the background from this product shot.&#8221; &#8220;Generate a lifestyle photo of my sunglasses with the beach avatar.&#8221; &#8220;Show me all my saved AI models.&#8221;<\/p>\n<p>That&#8217;s not a fantasy workflow \u2014 it&#8217;s what happens when you wire up a <strong>PixelPanda MCP server<\/strong> to Claude Code (or any MCP-compatible client). Your AI assistant becomes a direct line to background removal, AI product photography, and virtual try-on \u2014 no browser tabs, no manual uploads, no context-switching.<\/p>\n<p>In this guide, we&#8217;ll build one together from scratch in TypeScript, step by step.<\/p>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"what-is-mcp-and-why-should-you-care\">What Is MCP (and Why Should You Care)?<\/h2>\n<p>The <strong>Model Context Protocol (MCP)<\/strong> is an open standard from Anthropic that defines how AI applications talk to external tools and data sources. Think of it as USB-C for AI \u2014 one universal plug instead of a hundred bespoke adapters.<\/p>\n<p>MCP exists because LLMs have a frustrating limitation: they can <em>describe<\/em> how to remove a background from an image, but they can&#8217;t actually <em>do it<\/em>. MCP gives them hands.<\/p>\n<p>Here&#8217;s how the pieces fit together:<\/p>\n<ul>\n<li>An <strong>MCP client<\/strong> (Claude Code, Claude Desktop, Cursor) connects to an <strong>MCP server<\/strong><\/li>\n<li>The server advertises its capabilities \u2014 tools, resources, and prompts<\/li>\n<li>The AI model decides when and how to call those capabilities based on your conversation<\/li>\n<\/ul>\n<p>No hardcoded workflows. Just natural language driving real API calls.<\/p>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p><strong>Panda Tip:<\/strong> You don&#8217;t need to understand the full MCP spec to build a server. The SDK handles all the protocol negotiation. You just define your tools and the SDK does the rest.<\/p><\/blockquote>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"what-can-an-mcp-server-expose\">What Can an MCP Server Expose?<\/h2>\n<p>An MCP server advertises three types of capabilities:<\/p>\n<table style=\"width: 100%;border-collapse: collapse;margin: 24px 0;font-size: 0.95em\">\n<thead>\n<tr>\n<th style=\"background-color: #f8f9fa;padding: 12px 16px;text-align: left;border-bottom: 2px solid #e2e8f0;font-weight: 600\">Capability<\/th>\n<th style=\"background-color: #f8f9fa;padding: 12px 16px;text-align: left;border-bottom: 2px solid #e2e8f0;font-weight: 600\">What It Is<\/th>\n<th style=\"background-color: #f8f9fa;padding: 12px 16px;text-align: left;border-bottom: 2px solid #e2e8f0;font-weight: 600\">Example<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><strong>Tools<\/strong><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Functions the AI can call<\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Remove a background, generate a product photo<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><strong>Resources<\/strong><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Data the AI can browse<\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Your avatar library, product catalog<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><strong>Prompts<\/strong><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Reusable workflow templates<\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">&#8220;Run a full product shoot for item X&#8221;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For our PixelPanda server, we&#8217;ll focus on <strong>tools<\/strong> \u2014 they&#8217;re the most impactful and the easiest to understand. Here&#8217;s what we&#8217;re building:<\/p>\n<table style=\"width: 100%;border-collapse: collapse;margin: 24px 0;font-size: 0.95em\">\n<thead>\n<tr>\n<th style=\"background-color: #f8f9fa;padding: 12px 16px;text-align: left;border-bottom: 2px solid #e2e8f0;font-weight: 600\">Tool<\/th>\n<th style=\"background-color: #f8f9fa;padding: 12px 16px;text-align: left;border-bottom: 2px solid #e2e8f0;font-weight: 600\">What It Does<\/th>\n<th style=\"background-color: #f8f9fa;padding: 12px 16px;text-align: left;border-bottom: 2px solid #e2e8f0;font-weight: 600\">Credits<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">list_avatars<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Browse your saved AI avatar library<\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Free<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">remove_background<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Strip backgrounds to transparent PNG<\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">1\/image<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">generate_product_photo<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">AI marketing photography with avatar + product<\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">1\/image<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Once you see the pattern for these three, adding more tools (virtual try-on, scene generation, batch processing) is copy-paste simple.<\/p>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p><strong>Panda Tip:<\/strong> We&#8217;re building a <strong>local STDIO server<\/strong> \u2014 it runs on your machine as a subprocess. No cloud deployment, no network config. The MCP client spawns the server process and they talk over stdin\/stdout. It&#8217;s the fastest path to a working integration.<\/p><\/blockquote>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"step-1-project-setup\">Step 1: Project Setup<\/h2>\n<p>Let&#8217;s get the scaffolding in place. Create a new directory and install dependencies:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>mkdir pixelpanda-mcp &amp;&amp; cd pixelpanda-mcp\nnpm init -y\nnpm install @modelcontextprotocol\/sdk zod\nnpm install -D typescript @types\/node\nnpx tsc --init<\/code><\/pre>\n<p>Open <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">tsconfig.json<\/code> and set these four values (leave everything else as default):<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>{\n  &quot;compilerOptions&quot;: {\n    &quot;target&quot;: &quot;ES2022&quot;,\n    &quot;module&quot;: &quot;Node16&quot;,\n    &quot;moduleResolution&quot;: &quot;Node16&quot;,\n    &quot;outDir&quot;: &quot;.\/dist&quot;\n  }\n}<\/code><\/pre>\n<p>Now create a <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">src\/index.ts<\/code> file. This single file will hold our entire server \u2014 MCP servers are intentionally compact.<\/p>\n<p><strong>What you should see:<\/strong> Your project directory should have <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">node_modules\/<\/code>, <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">package.json<\/code>, <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">tsconfig.json<\/code>, and an empty <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">src\/index.ts<\/code>.<\/p>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"step-2-imports-and-configuration\">Step 2: Imports and Configuration<\/h2>\n<p>Every request to PixelPanda&#8217;s API v2 needs a Bearer token. Let&#8217;s set up the foundation \u2014 imports, the base URL, and a check that the token actually exists:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>import { McpServer } from &quot;@modelcontextprotocol\/sdk\/server\/mcp.js&quot;;\nimport { StdioServerTransport } from &quot;@modelcontextprotocol\/sdk\/server\/stdio.js&quot;;\nimport { z } from &quot;zod&quot;;\n\n\/\/ PixelPanda API v2 base URL\nconst PIXELPANDA_BASE_URL = &quot;https:\/\/pixelpanda.ai\/api\/v2&quot;;\n\n\/\/ Pull your API token from the environment (never hardcode it)\nconst API_TOKEN = process.env.PIXELPANDA_API_TOKEN;\n\nif (!API_TOKEN) {\n  console.error(&quot;PIXELPANDA_API_TOKEN environment variable is required&quot;);\n  process.exit(1);\n}<\/code><\/pre>\n<p>Three imports, one constant, one guard clause. That&#8217;s the entire config layer.<\/p>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p><strong>Panda Tip:<\/strong> Your API token looks like <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">pk_live_xxxxxxxxxxxx<\/code>. Grab it from <a href=\"https:\/\/pixelpanda.ai\/dashboard?tab=settings\">Dashboard Settings<\/a>. You need a paid plan (the $5 Try It pack works) \u2014 API v2 requires at least Starter-tier access.<\/p><\/blockquote>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"step-3-the-api-helper\">Step 3: The API Helper<\/h2>\n<p>We&#8217;ll make a lot of authenticated HTTP calls, so let&#8217;s write a small wrapper that handles the Bearer token and error checking in one place:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>async function ppFetch(path: string, options: RequestInit = {}): Promise&lt;Response&gt; {\n  const url = `${PIXELPANDA_BASE_URL}${path}`;\n\n  const response = await fetch(url, {\n    ...options,\n    headers: {\n      Authorization: `Bearer ${API_TOKEN}`,\n      &quot;Content-Type&quot;: &quot;application\/json&quot;,\n      ...options.headers,\n    },\n  });\n\n  \/\/ Surface API errors clearly so the AI can report them\n  if (!response.ok) {\n    const body = await response.text();\n    throw new Error(`PixelPanda API error ${response.status}: ${body}`);\n  }\n\n  return response;\n}<\/code><\/pre>\n<p>Every tool we build will use <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">ppFetch(\"\/some-endpoint\")<\/code> instead of raw <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">fetch()<\/code>. The auth header, error handling, and base URL are all baked in.<\/p>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"step-4-the-job-poller\">Step 4: The Job Poller<\/h2>\n<p>Here&#8217;s something important about PixelPanda&#8217;s generation endpoints: <strong>they&#8217;re asynchronous<\/strong>. When you request a product photo, the API immediately returns a <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">job_id<\/code>, and you poll <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">\/jobs\/{id}<\/code> until it&#8217;s done.<\/p>\n<p>Let&#8217;s write a reusable poller so our tool handlers stay clean:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>async function pollJobStatus(\n  jobId: string,\n  maxAttempts = 60,       \/\/ Give up after 60 checks\n  intervalMs = 3000       \/\/ Check every 3 seconds\n): Promise&lt;Record&lt;string, unknown&gt;&gt; {\n  for (let i = 0; i &lt; maxAttempts; i++) {\n    const res = await ppFetch(`\/jobs\/${jobId}`);\n    const job = (await res.json()) as Record&lt;string, unknown&gt;;\n\n    \/\/ Terminal states \u2014 either we&#x27;re done or something broke\n    if (job.status === &quot;completed&quot; || job.status === &quot;failed&quot;) {\n      return job;\n    }\n\n    await new Promise((resolve) =&gt; setTimeout(resolve, intervalMs));\n  }\n\n  throw new Error(`Job ${jobId} timed out after ${maxAttempts} attempts`);\n}<\/code><\/pre>\n<p>With 60 attempts at 3-second intervals, this gives jobs up to 3 minutes to complete. Most PixelPanda generations finish in 15-30 seconds.<\/p>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p><strong>Panda Tip:<\/strong> Why <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">console.error<\/code> and not <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">console.log<\/code>? STDIO transport uses stdout for MCP protocol messages. If you log to stdout, you&#8217;ll corrupt the protocol stream. Always use <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">stderr<\/code> for logging in MCP servers.<\/p><\/blockquote>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"step-5-create-the-mcp-server-instance\">Step 5: Create the MCP Server Instance<\/h2>\n<p>This is the simplest part. One call to the SDK:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>const server = new McpServer({\n  name: &quot;pixelpanda&quot;,\n  version: &quot;1.0.0&quot;,\n  description:\n    &quot;AI image processing \u2014 background removal, product photography, &quot; +\n    &quot;virtual try-on, and scene generation via PixelPanda&quot;,\n});<\/code><\/pre>\n<p>The SDK handles all protocol negotiation and capability advertisement behind the scenes. From here on, we just register tools.<\/p>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"step-6-build-the-list-avatars-tool\">Step 6: Build the <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">list_avatars<\/code> Tool<\/h2>\n<p>Let&#8217;s start simple. This tool is read-only \u2014 it fetches your saved AI avatars and returns their names and UUIDs. No credits consumed, no async polling. A perfect first tool.<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>server.tool(\n  &quot;list_avatars&quot;,                                 \/\/ Tool name\n  &quot;List all saved AI avatars available for &quot;      \/\/ Description (shown to the AI model)\n    + &quot;product photography and try-on. Returns &quot;\n    + &quot;UUIDs needed for generate_product_photo.&quot;,\n  {},                                             \/\/ No input parameters\n  async () =&gt; {\n    try {\n      const response = await ppFetch(&quot;\/avatars&quot;);\n      const avatars = await response.json() as Array&lt;{\n        uuid: string; name: string; style?: string;\n      }&gt;;<\/code><\/pre>\n<p>That&#8217;s the first half \u2014 make the API call and parse the response. Now let&#8217;s format the results for the AI to read:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>      \/\/ Format each avatar as a readable line with its UUID\n      const summary = avatars\n        .map((a) =&gt; `- **${a.name}** (${a.uuid}) \u2014 Style: ${a.style || &quot;custom&quot;}`)\n        .join(&quot;n&quot;);\n\n      return {\n        content: [{\n          type: &quot;text&quot; as const,\n          text: avatars.length === 0\n            ? &quot;No avatars found. Create one in the PixelPanda Dashboard first.&quot;\n            : `Found ${avatars.length} avatar(s):nn${summary}`,\n        }],\n      };\n    } catch (error) {\n      return {\n        content: [{ type: &quot;text&quot; as const, text: `Failed: ${error}` }],\n        isError: true,\n      };\n    }\n  }\n);<\/code><\/pre>\n<p>The structure of every tool response is the same: a <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">content<\/code> array with one or more text (or image) items, plus an optional <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">isError<\/code> flag. The AI reads the text and incorporates it into the conversation.<\/p>\n<p><strong>What you should see:<\/strong> When the AI calls this tool, it gets back something like:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>Found 3 avatar(s):\n\n- **Beach Model** (a1b2c3d4-...) \u2014 Style: lifestyle\n- **Studio Pro** (e5f6g7h8-...) \u2014 Style: professional\n- **Fitness Avatar** (i9j0k1l2-...) \u2014 Style: athletic<\/code><\/pre>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p><strong>Panda Tip:<\/strong> The tool description matters more than you&#8217;d think. MCP clients show it to the AI model so it knows <em>when<\/em> to use the tool. A vague description (&#8220;does avatar stuff&#8221;) means the model guesses wrong. Be specific about what the tool returns and why you&#8217;d use it.<\/p><\/blockquote>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"step-7-build-the-remove-background-tool\">Step 7: Build the <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">remove_background<\/code> Tool<\/h2>\n<p>Now let&#8217;s do something destructive \u2014 well, destructive to backgrounds. This tool takes an image URL, submits it for processing, polls until it&#8217;s done, and returns the transparent PNG.<\/p>\n<p>First, we define the tool with its input schema:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>server.tool(\n  &quot;remove_background&quot;,\n  &quot;Remove the background from an image. Accepts a public image URL, &quot;\n    + &quot;returns a transparent PNG. Costs 1 credit.&quot;,\n  {\n    \/\/ Zod schema \u2014 the AI sees this as the tool&#x27;s input contract\n    image_url: z.string().url().describe(&quot;Public URL of the image to process&quot;),\n  },\n  async ({ image_url }) =&gt; {\n    try {\n      \/\/ Step 1: Fetch the image and convert to base64\n      const imageResponse = await fetch(image_url);\n      const imageBuffer = await imageResponse.arrayBuffer();\n      const base64Image = Buffer.from(imageBuffer).toString(&quot;base64&quot;);<\/code><\/pre>\n<p>We need the image as base64 because that&#8217;s what the jobs endpoint expects. Next, submit the job:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>      \/\/ Step 2: Submit to PixelPanda as a background removal job\n      const jobResponse = await ppFetch(&quot;\/jobs&quot;, {\n        method: &quot;POST&quot;,\n        body: JSON.stringify({\n          product_image: base64Image,\n          product_filename: &quot;image.png&quot;,\n          category: &quot;other&quot;,\n          images_to_generate: 1,\n          custom_prompt: &quot;Remove background, transparent output&quot;,\n        }),\n      });\n      const job = await jobResponse.json() as { job_id: string };<\/code><\/pre>\n<p>And finally, poll for the result:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>      \/\/ Step 3: Wait for processing to complete\n      const result = await pollJobStatus(job.job_id);\n\n      if (result.status === &quot;failed&quot;) {\n        throw new Error(`Background removal failed: ${result.error}`);\n      }\n\n      const results = result.results as Array&lt;{ url: string }&gt;;\n\n      return {\n        content: [{\n          type: &quot;text&quot; as const,\n          text: `Background removed!nn**Result:** ${results[0]?.url}`,\n        }],\n      };\n    } catch (error) {\n      return {\n        content: [{ type: &quot;text&quot; as const, text: `Failed: ${error}` }],\n        isError: true,\n      };\n    }\n  }\n);<\/code><\/pre>\n<p><strong>The pattern is always the same:<\/strong> fetch image, base64-encode, submit job, poll, return URL. Every generation tool you add to this server will follow this exact rhythm.<\/p>\n<p><strong>What you should see:<\/strong> The AI returns a CDN URL to your processed image with a transparent background. Paste it in a browser and you&#8217;ll see a clean cutout \u2014 no background, ready for compositing.<\/p>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"step-8-build-the-generate-product-photo-tool\">Step 8: Build the <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">generate_product_photo<\/code> Tool<\/h2>\n<p>This is the showpiece. It combines a saved avatar with a saved product to create AI marketing photography \u2014 the avatar holding, using, or interacting with your product in a photorealistic scene.<\/p>\n<p>The input schema is richer here because the AI needs to know about UUIDs:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>server.tool(\n  &quot;generate_product_photo&quot;,\n  &quot;Generate AI product marketing photos. An avatar holds or uses &quot;\n    + &quot;your product in a professional scene. Requires avatar_uuid and &quot;\n    + &quot;product_uuid (use list_avatars and list_products to find them). &quot;\n    + &quot;Costs 1 credit per image.&quot;,\n  {\n    avatar_uuid: z.string().describe(&quot;UUID of a saved avatar&quot;),\n    product_uuid: z.string().describe(&quot;UUID of a saved product&quot;),\n    num_outputs: z.number().min(1).max(30).default(6)\n      .describe(&quot;Number of photos to generate (default: 6)&quot;),\n    prompt: z.string().optional()\n      .describe(&quot;Style direction, e.g. &#x27;outdoor summer&#x27; or &#x27;minimalist studio&#x27;&quot;),\n  },<\/code><\/pre>\n<p>Notice how the descriptions reference the other tools (&#8220;use list_avatars to find them&#8221;). This teaches the AI to chain tools together \u2014 it&#8217;ll call <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">list_avatars<\/code> first if it doesn&#8217;t have a UUID handy.<\/p>\n<p>The handler submits the request and kicks off polling \u2014 same rhythm as <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">remove_background<\/code>:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>  async ({ avatar_uuid, product_uuid, num_outputs, prompt }) =&gt; {\n    try {\n      \/\/ Submit the generation request\n      const response = await ppFetch(&quot;\/generate\/product-photo&quot;, {\n        method: &quot;POST&quot;,\n        body: JSON.stringify({\n          avatar_uuid,\n          product_uuid,\n          num_outputs: num_outputs ?? 6,\n          prompt: prompt ?? undefined,\n        }),\n      });\n\n      const job = await response.json() as {\n        job_id: string;\n        credits_reserved: number;\n        credits_remaining: number;\n      };\n\n      \/\/ Poll until all images are ready\n      const result = await pollJobStatus(job.job_id);<\/code><\/pre>\n<p>Once polling completes, we format the results as a numbered list and report credit usage:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>      if (result.status === &quot;failed&quot;) {\n        throw new Error(`Generation failed: ${result.error}`);\n      }\n\n      \/\/ Format each image as a numbered line with its scene label\n      const images = result.results as Array&lt;{ url: string; scene?: string }&gt;;\n      const list = images\n        .map((r, i) =&gt; `${i + 1}. ${r.scene || &quot;Photo&quot;}: ${r.url}`)\n        .join(&quot;n&quot;);\n\n      return {\n        content: [{\n          type: &quot;text&quot; as const,\n          text: `Generated ${images.length} photo(s):nn${list}nn`\n            + `Credits used: ${job.credits_reserved}`,\n        }],\n      };\n    } catch (error) {\n      return {\n        content: [{ type: &quot;text&quot; as const, text: `Failed: ${error}` }],\n        isError: true,\n      };\n    }\n  }\n);<\/code><\/pre>\n<p><strong>What you should see:<\/strong> The AI returns a numbered list of CDN URLs, each a photorealistic marketing shot of your avatar with your product. Something like:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>Generated 6 photo(s):\n\n1. Studio lighting: https:\/\/cdn.pixelpanda.ai\/gen\/abc123.png\n2. Outdoor setting: https:\/\/cdn.pixelpanda.ai\/gen\/def456.png\n3. Lifestyle scene: https:\/\/cdn.pixelpanda.ai\/gen\/ghi789.png\n...\n\nCredits used: 6<\/code><\/pre>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p><strong>Panda Tip:<\/strong> The <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">prompt<\/code> field is where you steer the creative direction. &#8220;Scandinavian minimalist&#8221; and &#8220;vibrant tropical beach&#8221; produce dramatically different results from the same avatar and product. Experiment!<\/p><\/blockquote>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"step-9-wire-up-the-transport\">Step 9: Wire Up the Transport<\/h2>\n<p>The last piece. Connect the server to STDIO so MCP clients can spawn it:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>async function main() {\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n  console.error(&quot;PixelPanda MCP server running on STDIO&quot;);\n}\n\nmain().catch((error) =&gt; {\n  console.error(&quot;Fatal error:&quot;, error);\n  process.exit(1);\n});<\/code><\/pre>\n<p>Build the project:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>npx tsc<\/code><\/pre>\n<p><strong>What you should see:<\/strong> A <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">dist\/index.js<\/code> file with no compilation errors. The server is ready to connect.<\/p>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"testing-your-server\">Testing Your Server<\/h2>\n<h3 id=\"configure-claude-code\">Configure Claude Code<\/h3>\n<p>Add the server to <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">~\/.claude\/claude_code_config.json<\/code>:<\/p>\n<pre style=\"background-color: #1a1b26;color: #c0caf5;padding: 20px 24px;border-radius: 8px;font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;font-size: 14px;line-height: 1.6;margin: 24px 0;border: 1px solid #2a2b3d\"><code>{\n  &quot;mcpServers&quot;: {\n    &quot;pixelpanda&quot;: {\n      &quot;command&quot;: &quot;node&quot;,\n      &quot;args&quot;: [&quot;\/absolute\/path\/to\/pixelpanda-mcp\/dist\/index.js&quot;],\n      &quot;env&quot;: {\n        &quot;PIXELPANDA_API_TOKEN&quot;: &quot;pk_live_your_token_here&quot;\n      }\n    }\n  }\n}<\/code><\/pre>\n<p>Restart Claude Code. Your three tools should appear in the available tools list.<\/p>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p><strong>Panda Tip:<\/strong> Use an <strong>absolute path<\/strong> in the <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">args<\/code> field. Relative paths break because MCP clients don&#8217;t necessarily launch from your project directory.<\/p><\/blockquote>\n<h3 id=\"try-these-conversations\">Try These Conversations<\/h3>\n<p>Here&#8217;s where the magic happens. Open Claude Code and talk to it naturally:<\/p>\n<p><strong>Browse your avatar library:<\/strong><\/p>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p>&#8220;What avatars do I have in PixelPanda?&#8221;<\/p><\/blockquote>\n<p>The AI calls <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">list_avatars<\/code> and shows you names, styles, and UUIDs.<\/p>\n<p><strong>Generate marketing photography:<\/strong><\/p>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p>&#8220;Generate 4 marketing photos of my ceramic vase with the studio-lighting avatar. Go for a warm, minimalist Scandinavian look.&#8221;<\/p><\/blockquote>\n<p>The AI finds the right UUIDs (calling <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">list_avatars<\/code> and <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">list_products<\/code> if needed), then fires <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">generate_product_photo<\/code> with your prompt. A few seconds later \u2014 four pixel-perfect product shots.<\/p>\n<p><strong>Remove a background:<\/strong><\/p>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p>&#8220;Remove the background from https:\/\/example.com\/sneakers.jpg&#8221;<\/p><\/blockquote>\n<p>One call to <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">remove_background<\/code>, a short wait, and you get a clean transparent PNG.<\/p>\n<p><strong>Chain operations together:<\/strong><\/p>\n<blockquote style=\"background-color: #f8f9fa;border-left: 4px solid #10b981;padding: 16px 20px;margin: 24px 0;border-radius: 0 8px 8px 0;font-size: 0.95em;line-height: 1.6\"><p>&#8220;Take this product photo, remove the background, then generate 6 marketing shots with my fitness avatar.&#8221;<\/p><\/blockquote>\n<p>This is where MCP really shines. The AI orchestrates multi-step workflows \u2014 calling tools in sequence, passing results forward \u2014 all driven by your conversational intent.<\/p>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"extending-the-server\">Extending the Server<\/h2>\n<p>You&#8217;ve built a working server with three tools. The pattern is established, and adding more is straightforward. Here are the natural next tools to wire up:<\/p>\n<table style=\"width: 100%;border-collapse: collapse;margin: 24px 0;font-size: 0.95em\">\n<thead>\n<tr>\n<th style=\"background-color: #f8f9fa;padding: 12px 16px;text-align: left;border-bottom: 2px solid #e2e8f0;font-weight: 600\">Tool<\/th>\n<th style=\"background-color: #f8f9fa;padding: 12px 16px;text-align: left;border-bottom: 2px solid #e2e8f0;font-weight: 600\">Endpoint<\/th>\n<th style=\"background-color: #f8f9fa;padding: 12px 16px;text-align: left;border-bottom: 2px solid #e2e8f0;font-weight: 600\">What It Does<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">generate_tryon<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">POST \/generate\/tryon<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Virtual try-on for clothing (avatar <em>wearing<\/em> the product)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">generate_scenes<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">POST \/generate\/scenes<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Product scenes without an avatar \u2014 great for furniture, food, electronics<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">list_products<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">GET \/products<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Browse your saved product library<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">upload_product<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">POST \/products<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Upload a new product (base64 image)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">batch_process<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\"><code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">POST \/batches<\/code><\/td>\n<td style=\"padding: 12px 16px;border-bottom: 1px solid #e2e8f0\">Bulk catalog processing for high-volume workflows<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Each one follows the exact same structure: define a name, write a description, declare a Zod input schema, and implement the async handler using <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">ppFetch<\/code> and <code style=\"background-color: #f0f0f0;color: #e4376b;padding: 2px 6px;border-radius: 4px;font-family: 'Fira Code', monospace;font-size: 0.9em\">pollJobStatus<\/code>.<\/p>\n<p>Beyond tools, you could add <strong>Resources<\/strong> (so the AI can passively browse your product catalog) or <strong>Prompts<\/strong> (reusable templates like &#8220;weekly product shoot&#8221; or &#8220;social media asset pack&#8221;).<\/p>\n<p>The full PixelPanda API v2 documentation is at <a href=\"https:\/\/pixelpanda.ai\">pixelpanda.ai<\/a>.<\/p>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"wrapping-up\">Wrapping Up<\/h2>\n<p>The gap between &#8220;I need a marketing photo&#8221; and &#8220;here&#8217;s your marketing photo&#8221; just collapsed from fifteen clicks to one sentence.<\/p>\n<p>That&#8217;s the real promise of MCP \u2014 not replacing your creative eye, but removing every tedious step between vision and pixel. Build the server, connect it to your AI client, and let the conversation drive the workflow.<\/p>\n<p>Your images are waiting.<\/p>\n<hr style=\"border: none;border-top: 1px solid #e2e8f0;margin: 40px 0\">\n<h2 id=\"more-mcp-server-guides\">More MCP Server Guides<\/h2>\n<p>Building MCP servers for other workflows? Check out our companion guides:<\/p>\n<ul>\n<li><strong><a href=\"https:\/\/autorank.so\/blog\/how-to-build-mcp-server-seo-content\/\" target=\"_blank\" rel=\"noopener\">How to Build an MCP Server for SEO Content Generation (AutoRank)<\/a><\/strong> \u2014 Analyze competitor blogs, find keyword gaps, and queue AI-generated articles from your AI assistant.<\/li>\n<li><strong><a href=\"https:\/\/shippost.ai\/blog\/how-to-build-mcp-server-twitter-growth\" target=\"_blank\" rel=\"noopener\">How to Build an MCP Server for Twitter\/X Growth (ShipPost)<\/a><\/strong> \u2014 Find the best tweets to reply to, draft voice-matched replies, and generate original content without leaving your editor.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Build a TypeScript MCP server that connects Claude Code to PixelPanda&#8217;s API for background removal, AI product photography, and virtual try-on \u2014 all driven by natural language.<\/p>\n","protected":false},"author":1,"featured_media":900,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"","rank_math_description":"Step-by-step guide to building an MCP server for AI image processing with PixelPanda. Remove backgrounds, generate product photos, and run virtual try-ons from Claude Code.","rank_math_focus_keyword":"mcp server image processing","footnotes":""},"categories":[238],"tags":[359,563,564,562,152],"class_list":["post-898","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-technology-development","tag-ai-developer-tools","tag-ai-image-processing-backend","tag-image-processing-pipeline","tag-mcp-server-ai","tag-pixelpanda-api"],"_links":{"self":[{"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/posts\/898","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/comments?post=898"}],"version-history":[{"count":1,"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/posts\/898\/revisions"}],"predecessor-version":[{"id":899,"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/posts\/898\/revisions\/899"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/media\/900"}],"wp:attachment":[{"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/media?parent=898"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/categories?post=898"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pixelpanda.ai\/blog\/wp-json\/wp\/v2\/tags?post=898"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}