<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bhavy Shekhaliya</title>
    <description>The latest articles on DEV Community by Bhavy Shekhaliya (@bhavyshekhaliya).</description>
    <link>https://dev.to/bhavyshekhaliya</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1638608%2Fbae60ede-857a-4050-9bbc-bc670f03506c.png</url>
      <title>DEV Community: Bhavy Shekhaliya</title>
      <link>https://dev.to/bhavyshekhaliya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bhavyshekhaliya"/>
    <language>en</language>
    <item>
      <title>I Wanted to Create an n8n Node. Then I Realized How Much Work Was Around It.</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Fri, 02 Oct 2026 02:33:19 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/i-wanted-to-create-an-n8n-node-then-i-realized-how-much-work-was-around-it-2n4c</link>
      <guid>https://dev.to/bhavyshekhaliya/i-wanted-to-create-an-n8n-node-then-i-realized-how-much-work-was-around-it-2n4c</guid>
      <description>&lt;p&gt;I love n8n.&lt;/p&gt;

&lt;p&gt;But creating a proper community node isn't just about writing the code.&lt;/p&gt;

&lt;p&gt;There’s the structure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The UI.&lt;/li&gt;
&lt;li&gt;Credentials.&lt;/li&gt;
&lt;li&gt;Operations and resources.&lt;/li&gt;
&lt;li&gt;Tooltips, hints and placeholders.&lt;/li&gt;
&lt;li&gt;Icons and naming.&lt;/li&gt;
&lt;li&gt;Builds.&lt;/li&gt;
&lt;li&gt;GitHub.&lt;/li&gt;
&lt;li&gt;npm.&lt;/li&gt;
&lt;li&gt;Publishing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then keeping everything clean as the node evolves.&lt;/p&gt;

&lt;p&gt;At some point, I thought:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There should be a better way to do all of this.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://nativeship.io" rel="noopener noreferrer"&gt;NativeShip&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is NativeShip?
&lt;/h2&gt;

&lt;p&gt;NativeShip helps you create, customize and ship your own &lt;strong&gt;n8n community nodes&lt;/strong&gt; without turning the whole process into a weekend-long setup project.&lt;/p&gt;

&lt;p&gt;You start with what you want your node to do.&lt;/p&gt;

&lt;p&gt;NativeShip helps take care of the structure and the boring parts around it, while still keeping your code and accounts under your control.&lt;/p&gt;

&lt;p&gt;Your code stays on &lt;strong&gt;your GitHub&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your package stays on &lt;strong&gt;your npm account&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;NativeShip doesn't take ownership of what you ship.&lt;/p&gt;

&lt;p&gt;That part was important to me.&lt;/p&gt;

&lt;h2&gt;
  
  
  I didn't want another "node generator"
&lt;/h2&gt;

&lt;p&gt;There are plenty of tools that can generate code.&lt;/p&gt;

&lt;p&gt;That wasn't really the problem.&lt;/p&gt;

&lt;p&gt;The harder part is creating a node that actually feels like a good n8n node.&lt;/p&gt;

&lt;p&gt;Something users can understand immediately.&lt;/p&gt;

&lt;p&gt;Something that follows n8n's conventions.&lt;/p&gt;

&lt;p&gt;Something you can maintain after version 1.0.&lt;/p&gt;

&lt;p&gt;That's why NativeShip focuses on the &lt;strong&gt;whole node creation lifecycle&lt;/strong&gt;, not just generating some TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Node Editor
&lt;/h2&gt;

&lt;p&gt;One of the features I'm most excited about is the &lt;strong&gt;Node Editor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can see your node inside a real n8n-style experience while you're working on it.&lt;/p&gt;

&lt;p&gt;So instead of constantly thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I wonder how this will actually look in n8n?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can see it.&lt;/p&gt;

&lt;p&gt;You can work on things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operations&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;li&gt;Field names&lt;/li&gt;
&lt;li&gt;Descriptions&lt;/li&gt;
&lt;li&gt;Tooltips&lt;/li&gt;
&lt;li&gt;Hints&lt;/li&gt;
&lt;li&gt;Placeholders&lt;/li&gt;
&lt;li&gt;Credentials&lt;/li&gt;
&lt;li&gt;Icons&lt;/li&gt;
&lt;li&gt;Node UX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a small thing, but it changes how you build.&lt;/p&gt;

&lt;p&gt;You stop thinking only about the code.&lt;/p&gt;

&lt;p&gt;You start thinking about the person who will actually use the node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following n8n conventions
&lt;/h2&gt;

&lt;p&gt;NativeShip is designed around n8n's official node development guidance.&lt;/p&gt;

&lt;p&gt;That includes the details that are easy to overlook when you're building alone.&lt;/p&gt;

&lt;p&gt;Things like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Resources and operations should feel predictable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fields should be understandable without making users guess what to enter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tooltips, hints and placeholders&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Small pieces of context can make a node dramatically easier to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Icons&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your node should feel like it belongs in the n8n ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The generated project follows the expected community-node structure instead of giving you a random pile of generated files.&lt;/p&gt;

&lt;p&gt;The goal isn't just:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Here is some code."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Here is a node you can actually work with."&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  And then there is publishing
&lt;/h2&gt;

&lt;p&gt;Getting the node ready is only half the story.&lt;/p&gt;

&lt;p&gt;You eventually need to build it, commit it, publish it and maintain it.&lt;/p&gt;

&lt;p&gt;NativeShip connects that workflow with your own GitHub and npm accounts.&lt;/p&gt;

&lt;p&gt;You stay in control.&lt;/p&gt;

&lt;p&gt;Your repository is yours.&lt;/p&gt;

&lt;p&gt;Your npm package is yours.&lt;/p&gt;

&lt;p&gt;Your credentials stay with you.&lt;/p&gt;

&lt;p&gt;And the workflow is designed around the official n8n community-node ecosystem rather than trying to create a completely separate system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Honestly, NativeShip started from a simple frustration.&lt;/p&gt;

&lt;p&gt;I wanted to make a good n8n node without spending most of my time dealing with everything around the node.&lt;/p&gt;

&lt;p&gt;And while working through that process, I realized other people probably had the same problem.&lt;/p&gt;

&lt;p&gt;Developers shouldn't have to fight tooling just to ship a good integration.&lt;/p&gt;

&lt;p&gt;They should be able to spend more time thinking about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should this node do?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is this build failing again?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  NativeShip is live 🚢
&lt;/h2&gt;

&lt;p&gt;NativeShip is now available for anyone who wants to create and ship their own n8n community node.&lt;/p&gt;

&lt;p&gt;You can check it out here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://nativeship.io" rel="noopener noreferrer"&gt;https://nativeship.io&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm still improving it every week, and I'm especially interested in hearing from people who have built n8n community nodes before.&lt;/p&gt;

&lt;p&gt;If you've ever thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I could build an n8n node, but..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'd love to know what came after the &lt;strong&gt;but&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Maybe that's the next thing NativeShip should make easier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build the node you wish existed. Ship it with confidence. 🚢&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>n8ncommunitynode</category>
      <category>n8nnodes</category>
      <category>automation</category>
    </item>
    <item>
      <title>AI accounts, 5 different rate limits, 0 idea which one still worked?</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Sat, 26 Sep 2026 03:34:46 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/ai-accounts-5-different-rate-limits-0-idea-which-one-still-worked-4505</link>
      <guid>https://dev.to/bhavyshekhaliya/ai-accounts-5-different-rate-limits-0-idea-which-one-still-worked-4505</guid>
      <description>&lt;p&gt;A few months ago my workflow looked something like this:&lt;/p&gt;

&lt;p&gt;3 Claude Pro accounts&lt;br&gt;
1 Codex accounts&lt;br&gt;
1 Cursor account&lt;/p&gt;

&lt;p&gt;Not because I'm hoarding subscriptions for fun it's because I lean on AI coding tools all day, and a single account's usage limit just doesn't survive a real working session. So the moment I hit a limit on one, I'd switch to the next.&lt;/p&gt;

&lt;p&gt;Sounds fine in theory. In practice it turned into a small nightmare.&lt;/p&gt;

&lt;p&gt;The actual problem&lt;/p&gt;

&lt;p&gt;I'd be deep in a refactor, hit a wall "you've reached your usage limit" and switch accounts. Cool. But an hour later I'd forget:&lt;/p&gt;

&lt;p&gt;Which account did I already burn through?&lt;br&gt;
Which one reset already?&lt;br&gt;
Did account #2 even have limit left, or did I use it yesterday and forget?&lt;/p&gt;

&lt;p&gt;So I'd end up opening each tool one by one, logging in, checking usage screens, closing tabs, trying to reconstruct a mental map of "who has what." Every single time I hit a limit. Multiple times a day.&lt;/p&gt;

&lt;p&gt;It's a dumb problem to have, but it was eating real time. Context-switching between 5 accounts across 3 different tools just to answer "which one can I use right now" is exactly the kind of friction that shouldn't exist for developers who are supposed to be moving fast.&lt;/p&gt;

&lt;p&gt;What I actually needed&lt;/p&gt;

&lt;p&gt;Not another dashboard to log into. Not another tab. I just wanted one place that could tell me, at a glance:&lt;/p&gt;

&lt;p&gt;Claude account 1 - X% used&lt;br&gt;
Claude account 2 - X% used&lt;br&gt;
Claude account 3 - X% used&lt;br&gt;
Codex account 1 - X% used&lt;br&gt;
Cursor - X% used&lt;/p&gt;

&lt;p&gt;That's it. No analytics, no fluff. Just "here's what's left, go use it."&lt;/p&gt;

&lt;p&gt;So I built it&lt;/p&gt;

&lt;p&gt;I ended up building a small desktop app that connects to your Claude, Codex, and Cursor accounts and shows live usage for each one in a single window. No more guessing, no more tab-hopping, no more "wait, did I already use this one today?"&lt;/p&gt;

&lt;p&gt;It's called UsageBuddy, and it exists purely because I got tired of doing manual account bookkeeping instead of writing code.&lt;/p&gt;

&lt;p&gt;If you're juggling multiple AI coding accounts too and if you use these tools seriously, you probably are this might save you the same headache it saved me.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.usagebuddy.com/?utm_source=devto" rel="noopener noreferrer"&gt;usagebuddy.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built this for myself first. If it's useful to you too, that's a bonus but I'd genuinely like to know if other devs are hitting the same wall, because right now it feels like something everyone running multiple AI accounts eventually runs into and just quietly tolerates.&lt;/p&gt;

&lt;p&gt;If that's you, don't wait until you're mid-refactor and rate-limited again to fix it. Go check your accounts now you might already be sitting on a maxed-out one without knowing it.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>productivity</category>
      <category>software</category>
    </item>
    <item>
      <title>How to Deploy a Remote MCP Server with Streamable HTTP</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Thu, 24 Sep 2026 09:06:48 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/how-to-deploy-a-remote-mcp-server-with-streamable-http-4n0h</link>
      <guid>https://dev.to/bhavyshekhaliya/how-to-deploy-a-remote-mcp-server-with-streamable-http-4n0h</guid>
      <description>&lt;p&gt;Building an MCP server locally is useful for development.&lt;/p&gt;

&lt;p&gt;Deploying it for production is a different job.&lt;/p&gt;

&lt;p&gt;Once the MCP server becomes remote, you need to think about endpoint shape, transport, authentication, API credentials, testing, logs, latency, versioning, and what happens when the upstream API changes.&lt;/p&gt;

&lt;p&gt;For API-backed MCP servers, a production deployment usually has one goal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;expose a stable remote MCP endpoint that AI clients can connect to, while the original API remains responsible for business logic and authorization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This guide walks through the deployment decisions I would check before shipping a remote MCP server over Streamable HTTP.&lt;/p&gt;




&lt;h2&gt;
  
  
  Local MCP servers and remote MCP servers solve different problems
&lt;/h2&gt;

&lt;p&gt;A local MCP server usually runs on one developer machine. Many local servers use &lt;code&gt;stdio&lt;/code&gt;, where the MCP client starts a local process and communicates through standard input and output.&lt;/p&gt;

&lt;p&gt;That model is helpful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you are building or debugging a server&lt;/li&gt;
&lt;li&gt;the tool only needs local machine access&lt;/li&gt;
&lt;li&gt;the server is for one developer&lt;/li&gt;
&lt;li&gt;the workflow does not need a shared hosted URL&lt;/li&gt;
&lt;li&gt;you want a fast feedback loop during development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A remote MCP server is different. It runs as a reachable service and exposes an endpoint that an MCP-compatible client can connect to over the network.&lt;/p&gt;

&lt;p&gt;That model makes more sense when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple users or clients need access&lt;/li&gt;
&lt;li&gt;your API is already cloud-hosted&lt;/li&gt;
&lt;li&gt;the server needs stable HTTPS&lt;/li&gt;
&lt;li&gt;your team wants logs and monitoring&lt;/li&gt;
&lt;li&gt;customers need to connect from their own environment&lt;/li&gt;
&lt;li&gt;the integration should survive laptop restarts and local setup issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither model is automatically better. They fit different stages. Local is good for development and private workflows. Remote is the production path for most SaaS API integrations.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Streamable HTTP changes
&lt;/h2&gt;

&lt;p&gt;Streamable HTTP gives a remote MCP server a network-friendly transport.&lt;/p&gt;

&lt;p&gt;Instead of launching a local process, the client connects to an HTTP endpoint. A production endpoint usually looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://mcp.example.com/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, for a default hosted endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-server-name.0mcp.dev/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you deploy with Streamable HTTP, your testing needs to include the full HTTP path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNS&lt;/li&gt;
&lt;li&gt;HTTPS&lt;/li&gt;
&lt;li&gt;endpoint routing&lt;/li&gt;
&lt;li&gt;request body handling&lt;/li&gt;
&lt;li&gt;response headers&lt;/li&gt;
&lt;li&gt;proxy behavior&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;request and response sizes&lt;/li&gt;
&lt;li&gt;client compatibility with the selected transport&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The MCP tool may be mapped correctly, but the deployment can still fail because the proxy drops streaming responses, the path is wrong, or the client is pointed at an old endpoint.&lt;/p&gt;

&lt;p&gt;Deployment is MCP code plus ordinary web-service work, with the added requirement that clients must discover and call MCP capabilities correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choose the endpoint shape early
&lt;/h2&gt;

&lt;p&gt;Pick a stable endpoint before production.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://mcp.yourcompany.com/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then write down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the environment: staging or production&lt;/li&gt;
&lt;li&gt;the transport: Streamable HTTP&lt;/li&gt;
&lt;li&gt;the exact path&lt;/li&gt;
&lt;li&gt;the owning team&lt;/li&gt;
&lt;li&gt;the current server version&lt;/li&gt;
&lt;li&gt;the upstream API environment&lt;/li&gt;
&lt;li&gt;the credential model&lt;/li&gt;
&lt;li&gt;the rollback plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds like admin work, but it prevents real bugs. If the team cannot tell which API environment the MCP server calls, or which version is currently active, debugging gets painful fast.&lt;/p&gt;

&lt;p&gt;For custom domains, also check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CNAME record&lt;/li&gt;
&lt;li&gt;domain verification&lt;/li&gt;
&lt;li&gt;TLS certificate status&lt;/li&gt;
&lt;li&gt;redirect behavior&lt;/li&gt;
&lt;li&gt;whether root domains are supported by your hosting model&lt;/li&gt;
&lt;li&gt;whether the &lt;code&gt;/mcp&lt;/code&gt; path reaches the MCP server rather than a website route&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For 0mcp specifically, eligible plans can use custom subdomains such as &lt;code&gt;mcp.example.com&lt;/code&gt;. Root domains are not supported. The default hosted endpoint format is &lt;code&gt;yourservername.0mcp.dev/mcp&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keep authentication separate from tool calls
&lt;/h2&gt;

&lt;p&gt;A remote MCP deployment has two authentication concerns.&lt;/p&gt;

&lt;p&gt;First, the MCP client needs to connect to the MCP server.&lt;/p&gt;

&lt;p&gt;Second, the MCP server needs to call the original API.&lt;/p&gt;

&lt;p&gt;For API-backed tools, avoid turning authentication routes into model-selected tools. The model should not need tools like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;login_user
refresh_token
create_api_key
get_secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those operations belong in the connection and credential flow, not in the ordinary tool list.&lt;/p&gt;

&lt;p&gt;For an API-to-MCP server, the common pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the client provides credentials at runtime&lt;/li&gt;
&lt;li&gt;the MCP server receives the tool call&lt;/li&gt;
&lt;li&gt;the MCP server forwards the relevant credential to the original API&lt;/li&gt;
&lt;li&gt;the original API validates identity, scopes, tenant access, and record permissions&lt;/li&gt;
&lt;li&gt;the MCP server returns the API result through the MCP interface&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Test API keys, Bearer tokens, and OAuth separately. A valid credential should work. A missing, expired, revoked, wrong-tenant, or under-scoped credential should fail safely.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://0mcp.io/" rel="noopener noreferrer"&gt;0mcp&lt;/a&gt;, existing API authentication continues to be used. API keys, Bearer tokens, and OAuth credentials are passed through during requests rather than stored by 0mcp. The hosted MCP server gives teams a Streamable HTTP endpoint, while the original API remains the source of truth for authorization and business rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deploy a focused capability surface
&lt;/h2&gt;

&lt;p&gt;Do not deploy every API endpoint because the importer can see it.&lt;/p&gt;

&lt;p&gt;Production remote servers need a smaller, cleaner surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tools that map to real user workflows&lt;/li&gt;
&lt;li&gt;clear names&lt;/li&gt;
&lt;li&gt;clear descriptions&lt;/li&gt;
&lt;li&gt;accurate input schemas&lt;/li&gt;
&lt;li&gt;predictable JSON responses&lt;/li&gt;
&lt;li&gt;narrow permissions&lt;/li&gt;
&lt;li&gt;separate review for write actions&lt;/li&gt;
&lt;li&gt;separate review for delete, bulk, admin, billing, or export operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a tool changes data, say that in the description.&lt;/p&gt;

&lt;p&gt;If a tool needs user confirmation, say that too.&lt;/p&gt;

&lt;p&gt;If two tools look almost the same, rename them or remove one. Similar tools are one of the easiest ways to confuse an agent during tool selection.&lt;/p&gt;

&lt;p&gt;For example, this pair is vague:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update_customer
modify_customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pair is better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update_customer_billing_email
update_customer_support_status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second pair tells the client what each tool changes. It also helps the developer write focused tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test the deployment before connecting production clients
&lt;/h2&gt;

&lt;p&gt;A production readiness test should cover connection, discovery, calls, auth, errors, and logs.&lt;/p&gt;

&lt;p&gt;Start with the connection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can a clean client connect to the remote endpoint?&lt;/li&gt;
&lt;li&gt;Does it use the expected transport?&lt;/li&gt;
&lt;li&gt;Does the server initialize?&lt;/li&gt;
&lt;li&gt;Does the endpoint work from outside your local network?&lt;/li&gt;
&lt;li&gt;Does the production proxy forward requests correctly?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then check discovery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are the expected tools visible?&lt;/li&gt;
&lt;li&gt;Are removed tools really absent?&lt;/li&gt;
&lt;li&gt;Are resources and prompts visible if your server exposes them?&lt;/li&gt;
&lt;li&gt;Are names unique?&lt;/li&gt;
&lt;li&gt;Are descriptions clear enough to choose from?&lt;/li&gt;
&lt;li&gt;Do input schemas match the API contract?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then run tool calls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;valid minimum input&lt;/li&gt;
&lt;li&gt;valid full input&lt;/li&gt;
&lt;li&gt;missing required input&lt;/li&gt;
&lt;li&gt;wrong type&lt;/li&gt;
&lt;li&gt;invalid enum&lt;/li&gt;
&lt;li&gt;empty result&lt;/li&gt;
&lt;li&gt;missing record&lt;/li&gt;
&lt;li&gt;upstream API timeout&lt;/li&gt;
&lt;li&gt;upstream rate limit&lt;/li&gt;
&lt;li&gt;repeated request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For write tools, use a safe environment and test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;invalid state transition&lt;/li&gt;
&lt;li&gt;duplicate request&lt;/li&gt;
&lt;li&gt;user without permission&lt;/li&gt;
&lt;li&gt;locked record&lt;/li&gt;
&lt;li&gt;payload with unexpected fields&lt;/li&gt;
&lt;li&gt;rollback or restore path if the change is unsafe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test should prove that the server behaves predictably across realistic cases, beyond one successful response.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monitor the server after launch
&lt;/h2&gt;

&lt;p&gt;Remote MCP servers need operational visibility.&lt;/p&gt;

&lt;p&gt;At minimum, you want to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request volume&lt;/li&gt;
&lt;li&gt;success and failure status&lt;/li&gt;
&lt;li&gt;authentication failures&lt;/li&gt;
&lt;li&gt;authorization failures&lt;/li&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;timeout frequency&lt;/li&gt;
&lt;li&gt;rate-limit responses&lt;/li&gt;
&lt;li&gt;most-used tools&lt;/li&gt;
&lt;li&gt;unused tools&lt;/li&gt;
&lt;li&gt;client source&lt;/li&gt;
&lt;li&gt;outbound response size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Individual logs help debug one call. Analytics help you see patterns.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one tool may fail because its schema no longer matches the API&lt;/li&gt;
&lt;li&gt;one client source may create most of the traffic&lt;/li&gt;
&lt;li&gt;a write tool may be rarely used and too risky to keep enabled&lt;/li&gt;
&lt;li&gt;a list tool may return responses that are too large&lt;/li&gt;
&lt;li&gt;a rate limit may appear only during a specific workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be careful with what you store. Logs should help debugging without storing secrets, request headers, raw credentials, or full response content unless your privacy model explicitly allows it.&lt;/p&gt;

&lt;p&gt;0mcp logs include fields such as time, capability name, source, status, duration or latency, and outbound size. The outbound field stores response size, not response content.&lt;/p&gt;




&lt;h2&gt;
  
  
  Version deployments like API-facing infrastructure
&lt;/h2&gt;

&lt;p&gt;An MCP server sits between clients and your API. When either side changes, users can feel it.&lt;/p&gt;

&lt;p&gt;Version these parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;imported API definition&lt;/li&gt;
&lt;li&gt;selected operations&lt;/li&gt;
&lt;li&gt;tool names&lt;/li&gt;
&lt;li&gt;tool descriptions&lt;/li&gt;
&lt;li&gt;input schemas&lt;/li&gt;
&lt;li&gt;authentication configuration&lt;/li&gt;
&lt;li&gt;hosted domain or endpoint settings&lt;/li&gt;
&lt;li&gt;resources and prompts&lt;/li&gt;
&lt;li&gt;production release notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some changes are safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;improving a description&lt;/li&gt;
&lt;li&gt;adding an optional field&lt;/li&gt;
&lt;li&gt;exposing a new read-only tool after testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some changes can break clients:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;renaming a tool&lt;/li&gt;
&lt;li&gt;removing a tool&lt;/li&gt;
&lt;li&gt;changing a required field&lt;/li&gt;
&lt;li&gt;changing enum values&lt;/li&gt;
&lt;li&gt;changing response shape&lt;/li&gt;
&lt;li&gt;moving the endpoint URL&lt;/li&gt;
&lt;li&gt;changing credential requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For breaking changes, keep a rollback path. If your platform supports restoring previous configuration versions, test that restore flow before you need it under pressure.&lt;/p&gt;

&lt;p&gt;0mcp supports saving, reviewing, and restoring configuration versions, so teams can manage selected operations and tool configuration changes without rebuilding the hosted endpoint from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hosted deployment versus self-hosting
&lt;/h2&gt;

&lt;p&gt;Self-hosting gives your team direct control over the runtime, network, deployment pipeline, and infrastructure choices.&lt;/p&gt;

&lt;p&gt;It also means your team owns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP server hosting&lt;/li&gt;
&lt;li&gt;HTTPS and routing&lt;/li&gt;
&lt;li&gt;transport behavior&lt;/li&gt;
&lt;li&gt;secrets management&lt;/li&gt;
&lt;li&gt;deployment automation&lt;/li&gt;
&lt;li&gt;uptime monitoring&lt;/li&gt;
&lt;li&gt;logs and analytics&lt;/li&gt;
&lt;li&gt;schema updates&lt;/li&gt;
&lt;li&gt;rollback paths&lt;/li&gt;
&lt;li&gt;production support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A hosted platform reduces that infrastructure burden. The tradeoff is that you work inside the platform's supported inputs, transports, configuration model, and product limits.&lt;/p&gt;

&lt;p&gt;For 0mcp, that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;supported inputs are Swagger 2.0, OpenAPI 3.0, OpenAPI 3.1, and Postman collections&lt;/li&gt;
&lt;li&gt;hosted transport is Streamable HTTP&lt;/li&gt;
&lt;li&gt;local &lt;code&gt;stdio&lt;/code&gt; servers are not supported&lt;/li&gt;
&lt;li&gt;tools, resources, and prompts are managed through the dashboard&lt;/li&gt;
&lt;li&gt;customers select which API operations to expose&lt;/li&gt;
&lt;li&gt;existing API auth remains in place through credential pass-through&lt;/li&gt;
&lt;li&gt;Playground, logs, analytics, and configuration versions support the hosted workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a practical fit for SaaS teams that want an MCP endpoint for their existing API but do not want to own the full infrastructure layer.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>programming</category>
      <category>mcp</category>
      <category>api</category>
    </item>
    <item>
      <title>What to Do When an API Has Too Many Endpoints for One MCP Server</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Sun, 13 Sep 2026 12:59:57 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/what-to-do-when-an-api-has-too-many-endpoints-for-one-mcp-server-1a77</link>
      <guid>https://dev.to/bhavyshekhaliya/what-to-do-when-an-api-has-too-many-endpoints-for-one-mcp-server-1a77</guid>
      <description>&lt;p&gt;Large APIs are where MCP design gets interesting.&lt;/p&gt;

&lt;p&gt;If your API has 15 endpoints, you can review each one by hand and decide which operations should become tools. If your API has 300 endpoints, exposing everything creates a different problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the MCP server becomes technically complete but hard for an AI client to use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An AI client does not see your product the way your backend team sees it. It sees a list of tool names, descriptions, and input schemas. If that list is too large or too repetitive, the client has to spend more effort choosing a tool than solving the user's request.&lt;/p&gt;

&lt;p&gt;The fix is not to dump the whole API into one MCP server. The fix is to design focused capability surfaces.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why too many endpoints become too many tools
&lt;/h2&gt;

&lt;p&gt;Most APIs grow around product history.&lt;/p&gt;

&lt;p&gt;You may have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;old routes kept for backward compatibility&lt;/li&gt;
&lt;li&gt;admin-only routes&lt;/li&gt;
&lt;li&gt;internal debugging routes&lt;/li&gt;
&lt;li&gt;several versions of similar endpoints&lt;/li&gt;
&lt;li&gt;narrowly scoped CRUD routes&lt;/li&gt;
&lt;li&gt;bulk import and export routes&lt;/li&gt;
&lt;li&gt;billing routes&lt;/li&gt;
&lt;li&gt;account-management routes&lt;/li&gt;
&lt;li&gt;support and reporting routes&lt;/li&gt;
&lt;li&gt;endpoints created for specific frontend screens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That API shape may be fine for developers. It is not automatically a good AI-facing interface.&lt;/p&gt;

&lt;p&gt;If you convert every endpoint into one MCP tool, the client may see dozens of similar options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_customer
get_customer_by_id
fetch_customer
list_customers
search_customers
admin_get_customer
get_customer_summary
get_customer_details
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if each tool works, the set becomes noisy. The model has to infer which tool is safest, which one returns the right fields, and which one matches the user's intent.&lt;/p&gt;

&lt;p&gt;That extra ambiguity leads to wrong calls, more retries, slower workflows, and harder debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Group endpoints by user workflow
&lt;/h2&gt;

&lt;p&gt;The first reduction pass should be workflow-based.&lt;/p&gt;

&lt;p&gt;Do not start with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which endpoints do we have?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which user task should this MCP server help with?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, a SaaS product may have several possible workflow groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer support context&lt;/li&gt;
&lt;li&gt;sales account research&lt;/li&gt;
&lt;li&gt;billing and invoice lookup&lt;/li&gt;
&lt;li&gt;project management updates&lt;/li&gt;
&lt;li&gt;workspace administration&lt;/li&gt;
&lt;li&gt;analytics reporting&lt;/li&gt;
&lt;li&gt;developer operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each group needs a different tool surface.&lt;/p&gt;

&lt;p&gt;A support workflow might need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_customer
list_customer_tickets
get_ticket
list_customer_subscriptions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It probably does not need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;delete_customer
create_invoice_adjustment
rotate_api_key
update_workspace_permissions
run_internal_report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grouping by workflow helps you remove endpoints without arguing about whether they are "important." Many endpoints are important to the product, but irrelevant to a specific AI workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Create focused MCP servers instead of one giant server
&lt;/h2&gt;

&lt;p&gt;For a large API, one MCP server can become a junk drawer.&lt;/p&gt;

&lt;p&gt;Focused servers are easier to reason about.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Support MCP server
- get_customer
- list_customer_tickets
- get_ticket
- create_ticket_note

Billing MCP server
- list_customer_invoices
- get_invoice
- get_subscription

Admin MCP server
- get_workspace_settings
- update_workspace_setting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These do not have to be separate products. They are separate AI-facing surfaces.&lt;/p&gt;

&lt;p&gt;The advantages are practical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer tools per client connection&lt;/li&gt;
&lt;li&gt;cleaner descriptions&lt;/li&gt;
&lt;li&gt;simpler permission review&lt;/li&gt;
&lt;li&gt;easier testing&lt;/li&gt;
&lt;li&gt;clearer logs&lt;/li&gt;
&lt;li&gt;safer rollout&lt;/li&gt;
&lt;li&gt;less chance that an agent chooses an unrelated admin or billing action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially useful when different roles should have different access. A support agent, sales rep, billing admin, and internal developer should not always see the same MCP tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Remove tools that do not map to a user intent
&lt;/h2&gt;

&lt;p&gt;Some endpoints exist because the frontend or backend needs them. That does not mean an AI agent needs them.&lt;/p&gt;

&lt;p&gt;Good MCP tools usually map to user requests like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Find this customer's open tickets."&lt;/li&gt;
&lt;li&gt;"Show the latest invoice."&lt;/li&gt;
&lt;li&gt;"Create a ticket from this report."&lt;/li&gt;
&lt;li&gt;"Update the status to resolved."&lt;/li&gt;
&lt;li&gt;"Summarize recent account activity."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weak MCP tools often map to implementation details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Call endpoint v2."&lt;/li&gt;
&lt;li&gt;"Patch object."&lt;/li&gt;
&lt;li&gt;"Run admin action."&lt;/li&gt;
&lt;li&gt;"Submit generic payload."&lt;/li&gt;
&lt;li&gt;"Fetch raw config."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When reviewing a large API, ask this for each endpoint:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What would a real user ask that should cause an AI client to call this tool?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you cannot write that request clearly, leave the endpoint out for now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Merge or rename confusing duplicates
&lt;/h2&gt;

&lt;p&gt;Large APIs often contain overlapping endpoints.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /customers/{id}
GET /customers/{customer_id}/profile
GET /crm/customers/{id}
GET /support/customers/{id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These might serve different backend needs, but they can create confusing MCP tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_customer
get_customer_profile
get_crm_customer
get_support_customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the agent's task is support context, expose the one that returns the right support-facing shape. Do not expose all four unless the differences are clear and necessary.&lt;/p&gt;

&lt;p&gt;If two tools must remain, name them by user-visible purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_customer_support_profile
get_customer_sales_profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is better than making the model guess the difference between &lt;code&gt;crm&lt;/code&gt; and &lt;code&gt;support&lt;/code&gt; from internal naming.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reduce schema complexity
&lt;/h2&gt;

&lt;p&gt;Tool overload can come from the number of tools, and it can also come from one huge schema.&lt;/p&gt;

&lt;p&gt;Watch for tools that accept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;arbitrary JSON blobs&lt;/li&gt;
&lt;li&gt;many unrelated optional fields&lt;/li&gt;
&lt;li&gt;broad filter objects&lt;/li&gt;
&lt;li&gt;raw query strings&lt;/li&gt;
&lt;li&gt;generic &lt;code&gt;data&lt;/code&gt; payloads&lt;/li&gt;
&lt;li&gt;fields whose meaning depends on another hidden field&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This kind of schema makes the AI client guess how to construct a safe request.&lt;/p&gt;

&lt;p&gt;Instead of one giant update tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update_customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consider smaller tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update_customer_billing_email
update_customer_support_status
update_customer_account_owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The smaller tools are easier to describe, easier to permission, and easier to test.&lt;/p&gt;

&lt;p&gt;There is a tradeoff. Too many tiny tools can also become noisy. The line I use is simple: split a tool when the actions have different permissions, side effects, or user intent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Treat sensitive operations as a separate surface
&lt;/h2&gt;

&lt;p&gt;Some endpoints should not be mixed into a general-purpose MCP server.&lt;/p&gt;

&lt;p&gt;Review these carefully:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deletes&lt;/li&gt;
&lt;li&gt;bulk updates&lt;/li&gt;
&lt;li&gt;exports&lt;/li&gt;
&lt;li&gt;billing changes&lt;/li&gt;
&lt;li&gt;permission changes&lt;/li&gt;
&lt;li&gt;API key creation&lt;/li&gt;
&lt;li&gt;OAuth client management&lt;/li&gt;
&lt;li&gt;account cancellation&lt;/li&gt;
&lt;li&gt;admin impersonation&lt;/li&gt;
&lt;li&gt;internal maintenance tasks&lt;/li&gt;
&lt;li&gt;notification or message sending&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These operations are not forbidden forever. They need stronger review.&lt;/p&gt;

&lt;p&gt;For sensitive tools, define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who can call the tool&lt;/li&gt;
&lt;li&gt;which credential scope is required&lt;/li&gt;
&lt;li&gt;whether a confirmation step is needed&lt;/li&gt;
&lt;li&gt;what records can be touched&lt;/li&gt;
&lt;li&gt;whether the action can be reversed&lt;/li&gt;
&lt;li&gt;how the action appears in logs&lt;/li&gt;
&lt;li&gt;how to test unauthorized calls&lt;/li&gt;
&lt;li&gt;how to roll back mistakes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a sensitive endpoint is useful only to internal staff, do not expose it in the same MCP server used by customers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Improve descriptions before adding more tools
&lt;/h2&gt;

&lt;p&gt;When an AI client chooses a tool, the description does real work.&lt;/p&gt;

&lt;p&gt;For a large API, weak descriptions compound fast.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gets customer.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get the support-facing profile for one customer by customer ID. Use this before checking tickets or subscription status.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Updates ticket.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Change the status of one support ticket after the user confirms the new status.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Descriptions should answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when to use the tool&lt;/li&gt;
&lt;li&gt;what input is needed&lt;/li&gt;
&lt;li&gt;what the tool returns&lt;/li&gt;
&lt;li&gt;whether the tool changes data&lt;/li&gt;
&lt;li&gt;whether the tool has side effects&lt;/li&gt;
&lt;li&gt;when another tool is a better fit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have 40 tools with vague descriptions, adding 20 more tools makes the system worse. Fix the existing tool interface first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use an allowlist, not a denylist
&lt;/h2&gt;

&lt;p&gt;For large APIs, I prefer an allowlist.&lt;/p&gt;

&lt;p&gt;A denylist says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Expose everything except these dangerous routes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is risky because new endpoints may appear later and slip into the MCP surface by default.&lt;/p&gt;

&lt;p&gt;An allowlist says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Expose only these selected operations."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is safer and easier to review.&lt;/p&gt;

&lt;p&gt;A simple allowlist can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;support_context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GET /v1/customers/{customer_id}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GET /v1/tickets&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GET /v1/tickets/{ticket_id}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POST /v1/tickets/{ticket_id}/notes&lt;/span&gt;

  &lt;span class="na"&gt;billing_lookup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GET /v1/invoices&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GET /v1/invoices/{invoice_id}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GET /v1/subscriptions/{subscription_id}&lt;/span&gt;

  &lt;span class="na"&gt;admin_controls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GET /v1/workspaces/{workspace_id}/settings&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PATCH /v1/workspaces/{workspace_id}/settings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the decision explicit. It also makes reviews cleaner when the API changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test the tool set as a set
&lt;/h2&gt;

&lt;p&gt;Testing each tool by itself is necessary, but it is not enough.&lt;/p&gt;

&lt;p&gt;When the API is large, you also need to test tool selection.&lt;/p&gt;

&lt;p&gt;Use prompts that resemble real user requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find open tickets for customer cus_123.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show the latest unpaid invoice for customer cus_123.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update ticket tick_456 to resolved.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can you delete this customer?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the client choose the expected tool?&lt;/li&gt;
&lt;li&gt;Does it confuse similar tools?&lt;/li&gt;
&lt;li&gt;Does it ask for missing IDs?&lt;/li&gt;
&lt;li&gt;Does it avoid high-risk actions without confirmation?&lt;/li&gt;
&lt;li&gt;Does it handle permission errors clearly?&lt;/li&gt;
&lt;li&gt;Does it recover from empty results?&lt;/li&gt;
&lt;li&gt;Does the server log show the tool name, status, latency, and response size?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the agent keeps choosing the wrong tool, do not patch around it only with prompts. Reduce the tool set, rename tools, improve descriptions, or split the server by workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  How 0mcp helps with large APIs
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://0mcp.io/" rel="noopener noreferrer"&gt;0mcp&lt;/a&gt;, teams can import a supported Swagger, OpenAPI, or Postman definition, review detected operations, and select which API functions should become MCP capabilities.&lt;/p&gt;

&lt;p&gt;That selection step matters for large APIs. The point is not to publish every route. The point is to choose the useful operations, refine names and descriptions, test the result in the Playground, and host the selected server over Streamable HTTP.&lt;/p&gt;

&lt;p&gt;0mcp currently supports hosted Streamable HTTP servers, not local &lt;code&gt;stdio&lt;/code&gt; servers. Existing API authentication continues to be used through API key, Bearer token, or OAuth pass-through. The original API still owns business logic, authorization, tenant boundaries, pagination, rate limits, and validation.&lt;/p&gt;

&lt;p&gt;That division is important. A hosted MCP workflow can make selection, hosting, testing, logs, analytics, and version management easier. It should not replace your product's permission model.&lt;/p&gt;

&lt;p&gt;For a deeper website guide on endpoint selection, see &lt;a href="https://0mcp.io/blog/choose-api-endpoints-for-mcp?utm_source=devto" rel="noopener noreferrer"&gt;how to choose which API endpoints to expose as MCP tools&lt;/a&gt;.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>programming</category>
      <category>mcp</category>
      <category>api</category>
    </item>
    <item>
      <title>Postman Collection to MCP: From Requests to MCP Tools</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Sat, 12 Sep 2026 19:29:57 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/postman-collection-to-mcp-from-requests-to-mcp-tools-4b5d</link>
      <guid>https://dev.to/bhavyshekhaliya/postman-collection-to-mcp-from-requests-to-mcp-tools-4b5d</guid>
      <description>&lt;p&gt;A Postman collection can be a surprisingly useful starting point for an MCP server.&lt;/p&gt;

&lt;p&gt;Many teams have Postman collections before they have polished OpenAPI documentation. The collection already contains working requests, paths, query parameters, headers, bodies, example responses, and authentication notes. That is enough to begin thinking about MCP tools.&lt;/p&gt;

&lt;p&gt;But there is a catch.&lt;/p&gt;

&lt;p&gt;A Postman request is still a developer artifact. An MCP tool is an AI-facing capability. Converting one into the other takes review, naming, schema cleanup, authentication decisions, testing, and production preparation.&lt;/p&gt;

&lt;p&gt;This article walks through the practical path from Postman requests to MCP tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start by cleaning the collection
&lt;/h2&gt;

&lt;p&gt;Before importing a Postman collection anywhere, clean it.&lt;/p&gt;

&lt;p&gt;A real collection often contains more than production-ready API requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;experiments&lt;/li&gt;
&lt;li&gt;duplicate requests&lt;/li&gt;
&lt;li&gt;old API versions&lt;/li&gt;
&lt;li&gt;internal debug endpoints&lt;/li&gt;
&lt;li&gt;local host URLs&lt;/li&gt;
&lt;li&gt;temporary headers&lt;/li&gt;
&lt;li&gt;personal API keys&lt;/li&gt;
&lt;li&gt;copied Bearer tokens&lt;/li&gt;
&lt;li&gt;test-only request bodies&lt;/li&gt;
&lt;li&gt;admin or destructive operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not treat the collection as safe because it works in Postman.&lt;/p&gt;

&lt;p&gt;Before using it for MCP, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this the current collection?&lt;/li&gt;
&lt;li&gt;Does it point to the intended API environment?&lt;/li&gt;
&lt;li&gt;Are request names clear?&lt;/li&gt;
&lt;li&gt;Are variables understandable?&lt;/li&gt;
&lt;li&gt;Are secrets removed?&lt;/li&gt;
&lt;li&gt;Are test-only requests removed?&lt;/li&gt;
&lt;li&gt;Are old endpoints removed or marked as legacy?&lt;/li&gt;
&lt;li&gt;Are destructive requests separated for review?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This cleanup step matters because the MCP tool list will inherit a lot of meaning from the collection. If the collection is messy, the MCP server will probably be messy too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understand what maps from Postman to MCP
&lt;/h2&gt;

&lt;p&gt;At a high level, each useful Postman request can become a candidate MCP tool.&lt;/p&gt;

&lt;p&gt;A request like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET {{baseUrl}}/v1/customers/{{customer_id}}/tickets?status=open
Authorization: Bearer {{token}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can become a tool like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list_open_customer_tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"List open support tickets for one customer."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The customer ID to search tickets for."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Maximum number of tickets to return."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mapping includes more than method and URL.&lt;/p&gt;

&lt;p&gt;You need to review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request name&lt;/li&gt;
&lt;li&gt;folder name&lt;/li&gt;
&lt;li&gt;HTTP method&lt;/li&gt;
&lt;li&gt;path variables&lt;/li&gt;
&lt;li&gt;query parameters&lt;/li&gt;
&lt;li&gt;headers&lt;/li&gt;
&lt;li&gt;request body&lt;/li&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;example response&lt;/li&gt;
&lt;li&gt;expected error behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Postman gives you the raw request shape. MCP needs a clear tool contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  Map path variables into required inputs
&lt;/h2&gt;

&lt;p&gt;Path variables usually become required tool inputs.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /v1/customers/{{customer_id}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should map to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The unique ID of the customer to retrieve."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the endpoint cannot run without &lt;code&gt;customer_id&lt;/code&gt;, the MCP schema should mark it as required.&lt;/p&gt;

&lt;p&gt;Bad schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The customer ID from your application."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Path variables deserve clear descriptions because the AI client may have several IDs in context. &lt;code&gt;customer_id&lt;/code&gt;, &lt;code&gt;workspace_id&lt;/code&gt;, &lt;code&gt;ticket_id&lt;/code&gt;, and &lt;code&gt;invoice_id&lt;/code&gt; should not be blurred into a generic &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Map query parameters into optional filters
&lt;/h2&gt;

&lt;p&gt;Query parameters often become optional tool inputs.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /v1/tickets?customer_id={{customer_id}}&amp;amp;status={{status}}&amp;amp;limit={{limit}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Candidate schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Return tickets for this customer."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resolved"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Optional ticket status filter."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maximum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Maximum number of tickets to return."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good query-parameter mapping should answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which filters are required for safe use?&lt;/li&gt;
&lt;li&gt;Which filters are optional?&lt;/li&gt;
&lt;li&gt;Are enum values documented?&lt;/li&gt;
&lt;li&gt;Are default limits safe?&lt;/li&gt;
&lt;li&gt;Can the request return too much data?&lt;/li&gt;
&lt;li&gt;Is pagination clear?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For AI clients, unbounded list endpoints are risky. If your API supports &lt;code&gt;limit&lt;/code&gt;, &lt;code&gt;cursor&lt;/code&gt;, &lt;code&gt;page&lt;/code&gt;, or &lt;code&gt;offset&lt;/code&gt;, make those fields clear.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handle request bodies carefully
&lt;/h2&gt;

&lt;p&gt;Postman bodies often contain example payloads.&lt;/p&gt;

&lt;p&gt;That does not automatically mean the MCP tool should accept the same raw JSON blob.&lt;/p&gt;

&lt;p&gt;A request like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /v1/tickets
Content-Type: application/json

{
  "customer_id": "{{customer_id}}",
  "subject": "{{subject}}",
  "priority": "{{priority}}",
  "message": "{{message}}"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"create_support_ticket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Create a support ticket for a customer."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The customer the ticket belongs to."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Short ticket subject."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"normal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ticket priority."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Initial support message."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid schemas that accept one giant &lt;code&gt;payload&lt;/code&gt; object unless the API genuinely needs arbitrary JSON. A specific schema gives the AI client better boundaries and gives your team better validation tests.&lt;/p&gt;

&lt;p&gt;For write operations, the description should also say what changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do not turn auth requests into normal tools
&lt;/h2&gt;

&lt;p&gt;Many Postman collections contain requests like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /login
POST /oauth/token
POST /refresh-token
GET /api-keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are usually not good MCP tools.&lt;/p&gt;

&lt;p&gt;Authentication should be part of the runtime connection and request flow. The model should not need to call &lt;code&gt;login&lt;/code&gt; before using product capabilities.&lt;/p&gt;

&lt;p&gt;For API-backed MCP tools, the safer pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the user or client provides credentials through the client flow&lt;/li&gt;
&lt;li&gt;the MCP server receives a tool call&lt;/li&gt;
&lt;li&gt;the MCP server passes the credential to the original API&lt;/li&gt;
&lt;li&gt;the original API enforces identity, scopes, tenant access, and record permissions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When reviewing a Postman collection, remove personal tokens and secrets from the export. Keep variables like &lt;code&gt;{{token}}&lt;/code&gt; or &lt;code&gt;{{apiKey}}&lt;/code&gt; as placeholders, not real credentials.&lt;/p&gt;

&lt;p&gt;Then test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing API key&lt;/li&gt;
&lt;li&gt;invalid API key&lt;/li&gt;
&lt;li&gt;expired Bearer token&lt;/li&gt;
&lt;li&gt;revoked OAuth access&lt;/li&gt;
&lt;li&gt;insufficient scope&lt;/li&gt;
&lt;li&gt;wrong tenant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authentication that works in Postman with your personal token may fail in MCP for a customer credential. Test that before production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Select useful operations, not every request
&lt;/h2&gt;

&lt;p&gt;A Postman collection can contain a lot of requests that are useful for developers and bad for AI agents.&lt;/p&gt;

&lt;p&gt;Start with a small workflow.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Let an AI support assistant look up customer context and create ticket notes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Useful requests might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /customers/{customer_id}
GET /tickets?customer_id={customer_id}
GET /tickets/{ticket_id}
POST /tickets/{ticket_id}/notes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requests to exclude from the first release might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELETE /customers/{customer_id}
POST /admin/reindex
PATCH /users/{user_id}/role
GET /internal/debug
POST /oauth/token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core selection rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A request should become an MCP tool only when it maps to a clear, useful, authorized AI capability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The tool list is an allowlist. Treat it like a product and security decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rename tools for the AI client
&lt;/h2&gt;

&lt;p&gt;Postman request names are often written for humans browsing a collection.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get Customer
Create
Update v2
List
Old invoice route
Test request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those names are weak MCP tool names.&lt;/p&gt;

&lt;p&gt;Prefer names that are stable, specific, and action-oriented:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_customer
list_customer_tickets
create_ticket_note
get_customer_subscription
list_unpaid_invoices
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tool descriptions should add the missing context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List unpaid invoices for one customer. Use this when the user asks about outstanding billing or payment status.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI client should be able to choose the tool without reading your Postman folder structure.&lt;/p&gt;

&lt;p&gt;If two tools sound the same, fix the names before adding more tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test the imported tools
&lt;/h2&gt;

&lt;p&gt;After importing and selecting operations, test the tool set before connecting a real client workflow.&lt;/p&gt;

&lt;p&gt;For each tool, test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;valid minimum input&lt;/li&gt;
&lt;li&gt;valid full input&lt;/li&gt;
&lt;li&gt;missing required path variable&lt;/li&gt;
&lt;li&gt;invalid query value&lt;/li&gt;
&lt;li&gt;invalid enum&lt;/li&gt;
&lt;li&gt;empty response&lt;/li&gt;
&lt;li&gt;missing record&lt;/li&gt;
&lt;li&gt;unauthorized request&lt;/li&gt;
&lt;li&gt;wrong tenant&lt;/li&gt;
&lt;li&gt;rate limit&lt;/li&gt;
&lt;li&gt;timeout&lt;/li&gt;
&lt;li&gt;unexpected upstream error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For write tools, also test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate submission&lt;/li&gt;
&lt;li&gt;invalid state transition&lt;/li&gt;
&lt;li&gt;insufficient permission&lt;/li&gt;
&lt;li&gt;payload with extra fields&lt;/li&gt;
&lt;li&gt;payload missing required business fields&lt;/li&gt;
&lt;li&gt;behavior in a safe test environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then test discovery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are only the intended tools visible?&lt;/li&gt;
&lt;li&gt;Are tool names unique?&lt;/li&gt;
&lt;li&gt;Are descriptions specific?&lt;/li&gt;
&lt;li&gt;Are required inputs obvious?&lt;/li&gt;
&lt;li&gt;Are removed or sensitive requests absent?&lt;/li&gt;
&lt;li&gt;Are resources and prompts visible only if intended?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where Postman-derived tools either become reliable or stay as "requests that worked once on my machine."&lt;/p&gt;




&lt;h2&gt;
  
  
  Prepare the hosted server for production
&lt;/h2&gt;

&lt;p&gt;A hosted MCP server needs more than a successful import.&lt;/p&gt;

&lt;p&gt;Before production, confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the hosted endpoint is stable&lt;/li&gt;
&lt;li&gt;the server uses the expected transport&lt;/li&gt;
&lt;li&gt;HTTPS works&lt;/li&gt;
&lt;li&gt;authentication is tested with real runtime credential paths&lt;/li&gt;
&lt;li&gt;the upstream API environment is correct&lt;/li&gt;
&lt;li&gt;logs show enough detail to debug calls&lt;/li&gt;
&lt;li&gt;analytics can show request volume, error rate, latency, and capability usage&lt;/li&gt;
&lt;li&gt;selected tools are versioned&lt;/li&gt;
&lt;li&gt;rollback or restore is possible after a bad change&lt;/li&gt;
&lt;li&gt;the original API still enforces tenant, role, record, and action permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;a href="https://0mcp.io/" rel="noopener noreferrer"&gt;0mcp&lt;/a&gt;, teams can import Postman collections, review detected requests, select useful API operations, refine tools, test in the Playground, and host the MCP server over Streamable HTTP. Existing API authentication continues to be used through API key, Bearer token, or OAuth pass-through, and customer credentials are passed through during requests rather than stored by 0mcp.&lt;/p&gt;

&lt;p&gt;0mcp currently supports hosted Streamable HTTP servers, not local &lt;code&gt;stdio&lt;/code&gt; servers. The original API remains responsible for business logic, authorization, pagination, rate limits, tenant boundaries, and validation.&lt;/p&gt;

&lt;p&gt;For the website version of this workflow, see &lt;a href="https://0mcp.io/blog/postman-to-mcp?utm_source=devto" rel="noopener noreferrer"&gt;Postman to MCP&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>mcp</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Update MCP Tools When the Underlying API Changes</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Sun, 30 Aug 2026 02:54:20 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/how-to-update-mcp-tools-when-the-underlying-api-changes-4jf2</link>
      <guid>https://dev.to/bhavyshekhaliya/how-to-update-mcp-tools-when-the-underlying-api-changes-4jf2</guid>
      <description>&lt;p&gt;An MCP tool is only as reliable as the API contract behind it.&lt;/p&gt;

&lt;p&gt;If the underlying API changes, the tool can break even when the MCP server is still running. A renamed field, a new required parameter, a changed enum, a stricter permission rule, or a different response shape can all affect how an AI client calls the tool.&lt;/p&gt;

&lt;p&gt;For API-backed MCP servers, the safe update process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;detect the API change&lt;/li&gt;
&lt;li&gt;identify affected MCP tools&lt;/li&gt;
&lt;li&gt;classify the change as compatible or breaking&lt;/li&gt;
&lt;li&gt;update schemas, names, descriptions, and authentication details&lt;/li&gt;
&lt;li&gt;test valid and invalid calls&lt;/li&gt;
&lt;li&gt;publish a versioned update&lt;/li&gt;
&lt;li&gt;keep a rollback path&lt;/li&gt;
&lt;li&gt;monitor the first production calls&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is boring in the best way: existing users should not wake up to broken tool calls because an API route changed quietly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start by treating MCP tools as contracts
&lt;/h2&gt;

&lt;p&gt;An MCP tool is not a random wrapper around an endpoint. It is a contract exposed to an AI client.&lt;/p&gt;

&lt;p&gt;That contract includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tool name&lt;/li&gt;
&lt;li&gt;tool description&lt;/li&gt;
&lt;li&gt;input schema&lt;/li&gt;
&lt;li&gt;required fields&lt;/li&gt;
&lt;li&gt;optional fields&lt;/li&gt;
&lt;li&gt;enums&lt;/li&gt;
&lt;li&gt;default behavior&lt;/li&gt;
&lt;li&gt;response shape&lt;/li&gt;
&lt;li&gt;authentication expectations&lt;/li&gt;
&lt;li&gt;side effects&lt;/li&gt;
&lt;li&gt;error behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the underlying API changes any of those things, the MCP tool may need an update.&lt;/p&gt;

&lt;p&gt;For example, this API change looks small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- GET /v1/customers/{id}
&lt;/span&gt;&lt;span class="gi"&gt;+ GET /v1/customers/{customer_id}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if the MCP tool schema still expects &lt;code&gt;id&lt;/code&gt;, clients may call the tool with the wrong field.&lt;/p&gt;

&lt;p&gt;Another small-looking change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- status: "open" | "closed"
&lt;/span&gt;&lt;span class="gi"&gt;+ status: "open" | "pending" | "resolved"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can affect validation, tool descriptions, examples, and the user's mental model.&lt;/p&gt;

&lt;p&gt;The MCP server may still initialize. The tool may still be discoverable. The break appears when real calls start failing or returning unexpected data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Create an API change checklist
&lt;/h2&gt;

&lt;p&gt;When the API changes, scan for changes that affect MCP tools.&lt;/p&gt;

&lt;p&gt;I would check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;paths&lt;/li&gt;
&lt;li&gt;HTTP methods&lt;/li&gt;
&lt;li&gt;path parameters&lt;/li&gt;
&lt;li&gt;query parameters&lt;/li&gt;
&lt;li&gt;request bodies&lt;/li&gt;
&lt;li&gt;required fields&lt;/li&gt;
&lt;li&gt;field names&lt;/li&gt;
&lt;li&gt;field types&lt;/li&gt;
&lt;li&gt;enum values&lt;/li&gt;
&lt;li&gt;pagination behavior&lt;/li&gt;
&lt;li&gt;response objects&lt;/li&gt;
&lt;li&gt;error objects&lt;/li&gt;
&lt;li&gt;authentication scheme&lt;/li&gt;
&lt;li&gt;required scopes&lt;/li&gt;
&lt;li&gt;tenant or workspace rules&lt;/li&gt;
&lt;li&gt;rate limits&lt;/li&gt;
&lt;li&gt;timeout behavior&lt;/li&gt;
&lt;li&gt;deprecated endpoints&lt;/li&gt;
&lt;li&gt;removed endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the boring list that saves production pain.&lt;/p&gt;

&lt;p&gt;If you use OpenAPI or Swagger, diff the API definition. If the source is a Postman collection, compare the exported requests and variables. If your MCP server was hand-written, compare the code and tool schemas directly.&lt;/p&gt;

&lt;p&gt;The important part is to map API changes back to MCP capability changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Classify changes before updating tools
&lt;/h2&gt;

&lt;p&gt;Do not treat every API change the same way.&lt;/p&gt;

&lt;p&gt;I usually classify changes into three groups.&lt;/p&gt;

&lt;p&gt;Compatible changes can often be released with normal testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding an optional response field&lt;/li&gt;
&lt;li&gt;adding a new optional input&lt;/li&gt;
&lt;li&gt;improving descriptions&lt;/li&gt;
&lt;li&gt;exposing a new read-only tool&lt;/li&gt;
&lt;li&gt;adding a new endpoint without changing existing tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Review-required changes need closer testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;changing default pagination limits&lt;/li&gt;
&lt;li&gt;adding new enum values&lt;/li&gt;
&lt;li&gt;changing error messages&lt;/li&gt;
&lt;li&gt;tightening validation&lt;/li&gt;
&lt;li&gt;changing rate-limit behavior&lt;/li&gt;
&lt;li&gt;adding a write tool&lt;/li&gt;
&lt;li&gt;changing authentication scopes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Breaking changes need migration planning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;renaming a tool&lt;/li&gt;
&lt;li&gt;removing a tool&lt;/li&gt;
&lt;li&gt;removing an endpoint&lt;/li&gt;
&lt;li&gt;changing a required input&lt;/li&gt;
&lt;li&gt;changing a field type&lt;/li&gt;
&lt;li&gt;removing enum values&lt;/li&gt;
&lt;li&gt;changing response shape&lt;/li&gt;
&lt;li&gt;moving from one auth model to another&lt;/li&gt;
&lt;li&gt;changing tenant or role behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This classification helps your team decide whether the update can ship quietly or needs coordination with users.&lt;/p&gt;




&lt;h2&gt;
  
  
  Update input schemas first
&lt;/h2&gt;

&lt;p&gt;The input schema is where many API changes become visible to the AI client.&lt;/p&gt;

&lt;p&gt;Suppose your API changes a ticket update endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;PATCH /v1/tickets/{ticket_id}/status
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;{
&lt;span class="gd"&gt;-  "status": "closed"
&lt;/span&gt;&lt;span class="gi"&gt;+  "status": "resolved",
+  "resolution_reason": "fixed"
&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your MCP schema may need to change from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"closed"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The ticket to update."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resolved"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The new ticket status."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolution_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reason for resolving the ticket."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then decide if &lt;code&gt;resolution_reason&lt;/code&gt; should become required when &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;resolved&lt;/code&gt;. If your schema cannot express that cleanly, explain the rule in the tool description and enforce it in the API.&lt;/p&gt;

&lt;p&gt;The schema should guide the client. The API should still validate the request.&lt;/p&gt;




&lt;h2&gt;
  
  
  Update descriptions when behavior changes
&lt;/h2&gt;

&lt;p&gt;Descriptions are part of the interface.&lt;/p&gt;

&lt;p&gt;If the underlying API behavior changes, the tool description may also need to change.&lt;/p&gt;

&lt;p&gt;Example before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Close a support ticket.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mark a support ticket as resolved after the user confirms the resolution. Requires a resolution reason when status is resolved.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version tells the client when the tool is appropriate and what extra context it needs.&lt;/p&gt;

&lt;p&gt;Review descriptions when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the tool changes data&lt;/li&gt;
&lt;li&gt;a field becomes required&lt;/li&gt;
&lt;li&gt;a field has new accepted values&lt;/li&gt;
&lt;li&gt;the response includes new states&lt;/li&gt;
&lt;li&gt;a permission rule changes&lt;/li&gt;
&lt;li&gt;the tool should be used later or earlier in the workflow&lt;/li&gt;
&lt;li&gt;the operation becomes risky&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially important for AI clients because they choose tools based on names, descriptions, and schemas. A technically correct schema with an outdated description can still cause bad calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recheck authentication and authorization
&lt;/h2&gt;

&lt;p&gt;API changes often touch authentication quietly.&lt;/p&gt;

&lt;p&gt;A tool that worked yesterday may fail after:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a new scope requirement&lt;/li&gt;
&lt;li&gt;a new tenant check&lt;/li&gt;
&lt;li&gt;an OAuth audience change&lt;/li&gt;
&lt;li&gt;a token expiry rule change&lt;/li&gt;
&lt;li&gt;an API key permission update&lt;/li&gt;
&lt;li&gt;a stricter record-level authorization check&lt;/li&gt;
&lt;li&gt;a move from one auth header to another&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test the happy path, then test failure paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing API key&lt;/li&gt;
&lt;li&gt;invalid API key&lt;/li&gt;
&lt;li&gt;expired Bearer token&lt;/li&gt;
&lt;li&gt;revoked OAuth access&lt;/li&gt;
&lt;li&gt;insufficient scope&lt;/li&gt;
&lt;li&gt;wrong tenant&lt;/li&gt;
&lt;li&gt;authenticated user without record access&lt;/li&gt;
&lt;li&gt;authenticated user without action permission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authentication success does not guarantee authorization success. A user can be logged in and still be blocked from reading a record, updating billing, exporting data, or changing workspace settings.&lt;/p&gt;

&lt;p&gt;For API-backed MCP, the original API should remain the authority for identity, tenant boundaries, role checks, scopes, and record permissions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Check response changes too
&lt;/h2&gt;

&lt;p&gt;Request schemas get most of the attention, but response changes can break workflows too.&lt;/p&gt;

&lt;p&gt;Watch for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;renamed response fields&lt;/li&gt;
&lt;li&gt;removed response fields&lt;/li&gt;
&lt;li&gt;fields changing type&lt;/li&gt;
&lt;li&gt;dates changing format&lt;/li&gt;
&lt;li&gt;IDs changing format&lt;/li&gt;
&lt;li&gt;nested objects becoming arrays&lt;/li&gt;
&lt;li&gt;arrays becoming paginated objects&lt;/li&gt;
&lt;li&gt;error bodies changing shape&lt;/li&gt;
&lt;li&gt;empty results changing from &lt;code&gt;[]&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;large response payloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An AI client may rely on a response field to decide the next step.&lt;/p&gt;

&lt;p&gt;For example, if the response changes from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assignee_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_123"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your tool may still return valid JSON, but the workflow has changed. Test downstream prompts or agent actions that depend on the old shape.&lt;/p&gt;




&lt;h2&gt;
  
  
  Version the API and MCP configuration together
&lt;/h2&gt;

&lt;p&gt;There are three layers to track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the upstream API version&lt;/li&gt;
&lt;li&gt;the MCP configuration version&lt;/li&gt;
&lt;li&gt;the MCP protocol/client compatibility layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not blur them.&lt;/p&gt;

&lt;p&gt;The API version tells you what the backend supports. The MCP configuration version tells you which tools, schemas, descriptions, resources, and prompts are exposed. The protocol/client layer tells you whether the MCP server and client can communicate correctly.&lt;/p&gt;

&lt;p&gt;For each release, record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api_source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openapi-2026-08-26.yaml&lt;/span&gt;
  &lt;span class="na"&gt;api_environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
  &lt;span class="na"&gt;mcp_configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;support-tools-v12&lt;/span&gt;
  &lt;span class="na"&gt;changed_tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;update_ticket_status&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;list_customer_tickets&lt;/span&gt;
  &lt;span class="na"&gt;change_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;review-required&lt;/span&gt;
  &lt;span class="na"&gt;rollback_to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;support-tools-v11&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You do not need this exact format. You do need traceability.&lt;/p&gt;

&lt;p&gt;When a user reports "the agent can no longer update a ticket," you should be able to find which API change and which MCP configuration shipped together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test old workflows before publishing
&lt;/h2&gt;

&lt;p&gt;Every update should include regression tests for existing workflows.&lt;/p&gt;

&lt;p&gt;Use saved fixtures or test prompts such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find open tickets for customer cus_123.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update ticket tick_456 to resolved with reason "customer confirmed fix."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show the latest unpaid invoice for customer cus_123.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test at three levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tool discovery: does the client see the expected tools?&lt;/li&gt;
&lt;li&gt;schema validation: do valid and invalid inputs behave correctly?&lt;/li&gt;
&lt;li&gt;workflow behavior: can the client complete the same user task?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also test failure cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing required field&lt;/li&gt;
&lt;li&gt;invalid enum&lt;/li&gt;
&lt;li&gt;unauthorized credential&lt;/li&gt;
&lt;li&gt;wrong tenant&lt;/li&gt;
&lt;li&gt;missing record&lt;/li&gt;
&lt;li&gt;upstream timeout&lt;/li&gt;
&lt;li&gt;rate limit&lt;/li&gt;
&lt;li&gt;changed response shape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only test the newly changed tool, you may miss a workflow that depends on two or three tools together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keep rollback realistic
&lt;/h2&gt;

&lt;p&gt;Rollback is not always as simple as restoring the previous MCP configuration.&lt;/p&gt;

&lt;p&gt;If you changed only tool descriptions or selected operations, restoring an older configuration may be enough.&lt;/p&gt;

&lt;p&gt;If the upstream API removed a field or endpoint, the old MCP configuration may still fail because it depends on the old API contract.&lt;/p&gt;

&lt;p&gt;Ask before every release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can we restore the previous MCP configuration?&lt;/li&gt;
&lt;li&gt;Does the previous API behavior still exist?&lt;/li&gt;
&lt;li&gt;Did a database migration remove required data?&lt;/li&gt;
&lt;li&gt;Did auth scopes change permanently?&lt;/li&gt;
&lt;li&gt;Are old enum values still accepted?&lt;/li&gt;
&lt;li&gt;Can both old and new clients run during migration?&lt;/li&gt;
&lt;li&gt;Who owns the rollback decision?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Safe rollback usually requires both sides: MCP configuration and API compatibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  How 0mcp helps with safer updates
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://0mcp.io/" rel="noopener noreferrer"&gt;0mcp&lt;/a&gt;, teams can import supported Swagger, OpenAPI, or Postman definitions, select which API operations are exposed, edit tool names and descriptions, test in the Playground, and host the MCP server over Streamable HTTP.&lt;/p&gt;

&lt;p&gt;For updates, the useful part is configuration versioning. Teams can save configuration versions, review changes, and restore an earlier version when needed. Saving an updated MCP configuration changes the hosted server without requiring a rebuild or changing its URL.&lt;/p&gt;

&lt;p&gt;There is still an important boundary: 0mcp does not replace your API's own compatibility and authorization work. The original API remains responsible for business logic, tenant checks, permissions, pagination, rate limits, and validation.&lt;/p&gt;

&lt;p&gt;0mcp currently supports hosted Streamable HTTP servers, not local &lt;code&gt;stdio&lt;/code&gt; servers. Existing API authentication continues through API key, Bearer token, or OAuth pass-through, and customer credentials are passed through during requests rather than stored by 0mcp.&lt;/p&gt;

&lt;p&gt;For the full website version of this topic, see &lt;a href="https://0mcp.io/blog/mcp-server-versioning?utm_source=devto" rel="noopener noreferrer"&gt;MCP server versioning and safe updates&lt;/a&gt;.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>programming</category>
      <category>mcp</category>
      <category>api</category>
    </item>
    <item>
      <title>Mapping API Path, Query, Header, and Body Parameters to MCP Tool Schemas</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Sat, 29 Aug 2026 03:57:47 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/mapping-api-path-query-header-and-body-parameters-to-mcp-tool-schemas-48k5</link>
      <guid>https://dev.to/bhavyshekhaliya/mapping-api-path-query-header-and-body-parameters-to-mcp-tool-schemas-48k5</guid>
      <description>&lt;p&gt;An API operation can receive input from several places.&lt;/p&gt;

&lt;p&gt;Path parameters identify the record. Query parameters filter or paginate the result. Headers carry metadata or authentication. The request body contains structured data for create and update operations.&lt;/p&gt;

&lt;p&gt;An MCP tool should give the AI client one clear input schema.&lt;/p&gt;

&lt;p&gt;That is the mapping problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP API inputs
  path + query + headers + body

become

MCP tool input
  one structured schema the AI client can understand
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tutorial walks through that mapping with practical examples. The goal is to make the tool easy for an AI client to call without hiding the real API contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example API operation
&lt;/h2&gt;

&lt;p&gt;Imagine a project-management API with this endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PATCH /workspaces/{workspace_id}/projects/{project_id}/tasks/{task_id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It updates one task.&lt;/p&gt;

&lt;p&gt;The API accepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;path parameters for &lt;code&gt;workspace_id&lt;/code&gt;, &lt;code&gt;project_id&lt;/code&gt;, and &lt;code&gt;task_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;query parameters such as &lt;code&gt;notify_assignee&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;a request body with the fields to update;&lt;/li&gt;
&lt;li&gt;authentication through a Bearer token header;&lt;/li&gt;
&lt;li&gt;an optional request header such as &lt;code&gt;Idempotency-Key&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A shortened OpenAPI-style version might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/workspaces/{workspace_id}/projects/{project_id}/tasks/{task_id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;patch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;updateTask&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update a task&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;title,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;assignee,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;due&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;one&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;task."&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workspace_id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;project_id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;task_id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notify_assignee&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
            &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;header&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
              &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                  &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;todo&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;in_progress&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;blocked&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;done&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
                &lt;span class="na"&gt;assignee_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                &lt;span class="na"&gt;due_date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;date&lt;/span&gt;
              &lt;span class="na"&gt;minProperties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bearerAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API shape is split across the HTTP request. The MCP tool should present the editable parts in one schema.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: name the tool from the operation, not the route
&lt;/h2&gt;

&lt;p&gt;The route is useful for the adapter, but it is not a good tool name.&lt;/p&gt;

&lt;p&gt;This is weak:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"patch_workspaces_projects_tasks"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is clearer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"update_task"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your API has several update operations, use the object and action to remove ambiguity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;update_task&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update_task_status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assign_task&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reschedule_task&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right name depends on what the endpoint actually does. If the endpoint updates many fields, &lt;code&gt;update_task&lt;/code&gt; may be correct. If the endpoint only changes status, &lt;code&gt;update_task_status&lt;/code&gt; is better.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: bring path parameters into the input schema
&lt;/h2&gt;

&lt;p&gt;Path parameters usually identify the exact resource being addressed. In an MCP tool schema, they are usually required fields.&lt;/p&gt;

&lt;p&gt;From the route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/workspaces/{workspace_id}/projects/{project_id}/tasks/{task_id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wrk_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prj_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsk_789"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the MCP tool schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"workspace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The workspace that contains the project."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The project that contains the task."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The task to update."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"workspace_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the path identifiers explicit. Do not collapse them into one generic &lt;code&gt;id&lt;/code&gt; field if the API needs all three values. The AI client should not guess which ID belongs to which level.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: map query parameters as filters and options
&lt;/h2&gt;

&lt;p&gt;Query parameters often change how the operation behaves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;filtering;&lt;/li&gt;
&lt;li&gt;sorting;&lt;/li&gt;
&lt;li&gt;pagination;&lt;/li&gt;
&lt;li&gt;flags;&lt;/li&gt;
&lt;li&gt;optional behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the example, &lt;code&gt;notify_assignee&lt;/code&gt; controls whether the API sends a notification after the update.&lt;/p&gt;

&lt;p&gt;That should appear as an optional tool input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"notify_assignee"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Whether to notify the assigned user after the task is updated."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For list operations, query parameters may be the main tool inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /customers/{customer_id}/tickets?status=open&amp;amp;limit=20&amp;amp;cursor=abc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP schema might expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The customer whose tickets should be listed."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"closed"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Optional ticket status filter."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maximum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Maximum number of tickets to return."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cursor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pagination cursor from a previous response."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good query mapping keeps list tools bounded. If a search endpoint accepts unlimited free-form parameters, the agent may produce slow, broad, or invalid calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: treat authentication headers separately
&lt;/h2&gt;

&lt;p&gt;Headers are tricky because some are normal inputs and some are credentials.&lt;/p&gt;

&lt;p&gt;Authentication headers should not become normal tool inputs.&lt;/p&gt;

&lt;p&gt;Do not expose this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer token for the API request."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That would make the credential model-visible.&lt;/p&gt;

&lt;p&gt;Instead, the tool input should stay focused on the business operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wrk_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prj_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsk_789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blocked"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter or hosted MCP runtime should receive credentials through the authentication path and forward them to the upstream API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorization: Bearer &amp;lt;runtime credential&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The upstream API still enforces identity, tenant, role, record, and action permissions. The MCP schema should not become a place where the model supplies secrets.&lt;/p&gt;

&lt;p&gt;0mcp supports API key, Bearer token, and OAuth pass-through. Credentials are supplied through the MCP client at request time and passed to the original API rather than stored by 0mcp. That keeps the generated tool schema focused on the task inputs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: decide what to do with non-auth headers
&lt;/h2&gt;

&lt;p&gt;Some headers are not secrets. They may still matter.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Idempotency-Key&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X-Request-Id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Accept-Language&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;If-Match&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X-Client-Version&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not automatically expose every header to the AI client. Ask what role the header plays.&lt;/p&gt;

&lt;p&gt;Expose a header as a tool input when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the caller can safely provide it;&lt;/li&gt;
&lt;li&gt;it changes behavior in a useful way;&lt;/li&gt;
&lt;li&gt;the value is not secret;&lt;/li&gt;
&lt;li&gt;the format can be validated;&lt;/li&gt;
&lt;li&gt;the AI client understands why it exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;code&gt;Idempotency-Key&lt;/code&gt;, you may decide to generate it inside the MCP server instead of asking the AI client to provide it. That reduces friction and avoids duplicate-write bugs.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;Accept-Language&lt;/code&gt;, you might expose &lt;code&gt;language&lt;/code&gt; as a business-friendly field rather than the raw HTTP header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fr"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Preferred language for localized response text."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the adapter maps it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accept-Language: en
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema should describe the product-level input, not force the agent to think in low-level HTTP details when a cleaner field works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: map the request body into structured inputs
&lt;/h2&gt;

&lt;p&gt;For create and update operations, the request body often becomes the largest part of the tool schema.&lt;/p&gt;

&lt;p&gt;From the &lt;code&gt;PATCH /tasks/{task_id}&lt;/code&gt; example, the request body allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assignee_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;due_date&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The combined MCP input schema can include path, query, and body fields together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"update_task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Update the title, status, assignee, or due date for one task. Use this only after the user has identified the workspace, project, task, and requested change."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"workspace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The workspace that contains the project."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The project that contains the task."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The task to update."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New task title."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"todo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"in_progress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blocked"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New task status."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"assignee_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User ID of the new assignee."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"due_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New due date in YYYY-MM-DD format."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"notify_assignee"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Whether to notify the assignee after the update."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"workspace_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the body fields are optional in the schema because this is a patch operation. But the API should still reject a request that updates nothing. You can express that with validation logic if the schema format you use cannot represent it cleanly.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updateFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assignee_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;due_date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;updateFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Provide at least one task field to update.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool should guide the model toward valid updates without making every field required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: handle naming conflicts deliberately
&lt;/h2&gt;

&lt;p&gt;Naming conflicts happen often when you combine path, query, header, and body inputs into one schema.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PATCH /projects/{id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Request body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"external-project-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New project name"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there are two &lt;code&gt;id&lt;/code&gt; values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;path &lt;code&gt;id&lt;/code&gt;, which identifies the project being updated;&lt;/li&gt;
&lt;li&gt;body &lt;code&gt;id&lt;/code&gt;, which might represent an external ID or imported ID.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not expose both as &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use names that preserve meaning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prj_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"external_project_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ext_999"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New project name"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other common conflicts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;user_id&lt;/code&gt; in both path and body;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; in query and body;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;version&lt;/code&gt; in header and body;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;limit&lt;/code&gt; in query and nested request body;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; fields inside nested objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When in doubt, name the field by its role in the operation. The model should know whether it is selecting a resource, filtering a result, updating a value, or controlling request behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: build the API request from the tool input
&lt;/h2&gt;

&lt;p&gt;After the AI client sends the MCP tool input, the handler maps it back to the API request.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;update_task&lt;/code&gt;, the handler might do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateTaskTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;validateUpdateTaskInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/workspaces/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workspace_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/projects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tasks/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notify_assignee&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notify_assignee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notify_assignee&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assignee_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;due_date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mapTaskResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the direction clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the tool schema receives product-level inputs;&lt;/li&gt;
&lt;li&gt;the handler validates those inputs;&lt;/li&gt;
&lt;li&gt;the handler places each value in the correct HTTP location;&lt;/li&gt;
&lt;li&gt;authentication comes from runtime context;&lt;/li&gt;
&lt;li&gt;the API response becomes the tool result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid handlers that accept arbitrary paths, methods, headers, or bodies from the model. That turns a structured MCP tool back into a generic API proxy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9: validate before calling the API
&lt;/h2&gt;

&lt;p&gt;Validation should happen before the adapter sends the API request.&lt;/p&gt;

&lt;p&gt;At minimum, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;required path identifiers exist;&lt;/li&gt;
&lt;li&gt;string, boolean, integer, array, and object types are correct;&lt;/li&gt;
&lt;li&gt;enum values are allowed;&lt;/li&gt;
&lt;li&gt;date, email, URL, and ID formats match expectations;&lt;/li&gt;
&lt;li&gt;pagination limits stay within bounds;&lt;/li&gt;
&lt;li&gt;at least one update field exists for patch operations;&lt;/li&gt;
&lt;li&gt;body fields do not contain unsupported properties;&lt;/li&gt;
&lt;li&gt;non-auth headers are safe and well formed;&lt;/li&gt;
&lt;li&gt;authentication is present in runtime context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some validation belongs in the MCP schema. Some belongs in code. Some still belongs in the upstream API.&lt;/p&gt;

&lt;p&gt;The upstream API remains the final enforcement point for business rules. The MCP layer should prevent obvious bad calls and make errors easier to understand, but it should not replace the API's authorization and data validation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 10: map responses and errors clearly
&lt;/h2&gt;

&lt;p&gt;For a successful update, the API might return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsk_789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blocked"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fix webhook retries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assignee_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-26T10:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP tool result should preserve the useful fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsk_789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blocked"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fix webhook retries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assignee_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-26T10:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For errors, keep the categories distinct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;400&lt;/code&gt; means the request was invalid;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;401&lt;/code&gt; means authentication failed;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;403&lt;/code&gt; means the caller lacks permission;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;404&lt;/code&gt; means the resource was not found;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;409&lt;/code&gt; may mean a version or state conflict;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;429&lt;/code&gt; means the API rate limit was hit;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5xx&lt;/code&gt; means the upstream API failed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not turn every error into "Tool failed." The agent and the developer both need the failure to say what kind of problem happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 11: test the mapping with real tool calls
&lt;/h2&gt;

&lt;p&gt;Test each input location separately.&lt;/p&gt;

&lt;p&gt;For path parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;omit &lt;code&gt;workspace_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;use a valid &lt;code&gt;project_id&lt;/code&gt; with an invalid &lt;code&gt;task_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;try a task ID that belongs to another workspace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For query parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;omit optional fields;&lt;/li&gt;
&lt;li&gt;pass &lt;code&gt;notify_assignee: true&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;pass the wrong type, such as &lt;code&gt;"yes"&lt;/code&gt; instead of &lt;code&gt;true&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;test pagination boundaries on list tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test missing credentials;&lt;/li&gt;
&lt;li&gt;test expired credentials;&lt;/li&gt;
&lt;li&gt;test credentials without write permission;&lt;/li&gt;
&lt;li&gt;test a generated idempotency key if the operation supports it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For request bodies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;update one field;&lt;/li&gt;
&lt;li&gt;update multiple fields;&lt;/li&gt;
&lt;li&gt;send an invalid enum;&lt;/li&gt;
&lt;li&gt;send an unsupported field;&lt;/li&gt;
&lt;li&gt;send an empty patch body.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For responses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verify the result contains the fields the AI client needs;&lt;/li&gt;
&lt;li&gt;check empty and missing-resource cases;&lt;/li&gt;
&lt;li&gt;check rate-limit and timeout behavior;&lt;/li&gt;
&lt;li&gt;confirm sensitive headers, tokens, and internal debugging fields are not returned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing should answer a larger question than "does the API return 200?" An AI client has to discover the tool, send valid structured input, get a useful result, and understand failures.&lt;/p&gt;




&lt;h2&gt;
  
  
  How this works in 0mcp
&lt;/h2&gt;

&lt;p&gt;With 0mcp, the same mapping starts from a supported API definition or Postman collection.&lt;/p&gt;

&lt;p&gt;The hosted workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;import a Swagger 2.0, OpenAPI 3.0, OpenAPI 3.1, or Postman definition;&lt;/li&gt;
&lt;li&gt;review validation warnings and detected operations;&lt;/li&gt;
&lt;li&gt;select the API functions that should become AI-facing capabilities;&lt;/li&gt;
&lt;li&gt;create or update tools, resources, and prompts;&lt;/li&gt;
&lt;li&gt;edit tool names and descriptions where the imported wording needs work;&lt;/li&gt;
&lt;li&gt;use API key, Bearer token, or OAuth pass-through at runtime;&lt;/li&gt;
&lt;li&gt;test the hosted Streamable HTTP server in the Playground;&lt;/li&gt;
&lt;li&gt;review logs, analytics, and configuration versions as the API changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;0mcp currently supports hosted Streamable HTTP servers, not local &lt;code&gt;stdio&lt;/code&gt; servers. The original API remains responsible for business logic, authorization, pagination, rate limits, and data validation.&lt;/p&gt;

&lt;p&gt;If your OpenAPI contract has weak parameter definitions, fix the source document first. The &lt;a href="https://0mcp.io/blog/openapi-requirements-for-mcp?utm_source=devto" rel="noopener noreferrer"&gt;OpenAPI requirements guide&lt;/a&gt; covers the checks that matter before import.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Exposing auth headers as tool inputs
&lt;/h3&gt;

&lt;p&gt;Keep credentials out of the schema. Use runtime authentication and pass credentials to the upstream API from the server side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collapsing every identifier into &lt;code&gt;id&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;workspace_id&lt;/code&gt;, &lt;code&gt;project_id&lt;/code&gt;, &lt;code&gt;task_id&lt;/code&gt;, and similar names when the hierarchy matters. The AI client should not guess which ID goes where.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making patch fields required
&lt;/h3&gt;

&lt;p&gt;For update operations, identifiers are usually required, but editable fields may be optional. Add validation that requires at least one change instead of requiring every possible update field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ignoring query bounds
&lt;/h3&gt;

&lt;p&gt;List and search tools need limits, cursors, allowed filters, and clear defaults. An unbounded query tool is hard to test and easy to misuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Returning vague error messages
&lt;/h3&gt;

&lt;p&gt;Map authentication, authorization, validation, not-found, conflict, rate-limit, timeout, and upstream errors separately. Vague failures slow down debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;p&gt;Before publishing a parameter-mapped MCP tool, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;path parameters are required and clearly named;&lt;/li&gt;
&lt;li&gt;query parameters have defaults, enums, bounds, and descriptions;&lt;/li&gt;
&lt;li&gt;authentication headers stay out of model-visible inputs;&lt;/li&gt;
&lt;li&gt;safe non-auth headers are either generated server-side or exposed as product-level inputs;&lt;/li&gt;
&lt;li&gt;body fields preserve required fields, types, formats, enums, and nested objects;&lt;/li&gt;
&lt;li&gt;naming conflicts are resolved with meaningful field names;&lt;/li&gt;
&lt;li&gt;validation runs before the API request;&lt;/li&gt;
&lt;li&gt;API errors map to understandable tool errors;&lt;/li&gt;
&lt;li&gt;response fields give the AI client enough information to continue;&lt;/li&gt;
&lt;li&gt;valid, invalid, unauthorized, forbidden, missing-resource, timeout, and rate-limit cases are tested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good MCP tool schemas do not make the AI client think in raw HTTP. They give the client a clear set of product-level inputs, then let the adapter place each value in the correct part of the API request.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>mcp</category>
      <category>api</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Create an MCP Server for an API: From Operation Mapping to Tool Calls</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Wed, 26 Aug 2026 16:18:55 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/how-to-create-an-mcp-server-for-an-api-from-operation-mapping-to-tool-calls-1hh3</link>
      <guid>https://dev.to/bhavyshekhaliya/how-to-create-an-mcp-server-for-an-api-from-operation-mapping-to-tool-calls-1hh3</guid>
      <description>&lt;p&gt;If you already have a working API, you are most of the way toward an MCP server.&lt;/p&gt;

&lt;p&gt;The API already knows how to create records, fetch data, enforce permissions, validate inputs, and return responses. The MCP server adds a different interface on top: tools an AI client can discover and call with structured arguments.&lt;/p&gt;

&lt;p&gt;In this tutorial, I will walk through the practical mapping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API endpoint to MCP tool&lt;/li&gt;
&lt;li&gt;path, query, and body parameters to tool input schema&lt;/li&gt;
&lt;li&gt;API authentication to runtime credential handling&lt;/li&gt;
&lt;li&gt;API response to MCP tool result&lt;/li&gt;
&lt;li&gt;API errors to useful tool errors&lt;/li&gt;
&lt;li&gt;local or hosted MCP testing before production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will use a small support-ticket API as the example because it is easy to understand and still covers the parts that matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  The example API
&lt;/h2&gt;

&lt;p&gt;Imagine your SaaS product has these API operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET  /tickets/{ticket_id}
POST /tickets
GET  /customers/{customer_id}/tickets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user workflows are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;get the current status of one ticket;&lt;/li&gt;
&lt;li&gt;create a new support ticket;&lt;/li&gt;
&lt;li&gt;list tickets for a customer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A basic OpenAPI-style summary might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/tickets/{ticket_id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;getTicket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get one support ticket&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket_id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;include_comments&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
      &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bearerAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="na"&gt;/tickets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;createTicket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a support ticket&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
              &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;customer_id&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;title&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;description&lt;/span&gt;
              &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;customer_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                  &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;low&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;normal&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;high&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bearerAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us enough to map real API behavior into tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: choose the operations that should become tools
&lt;/h2&gt;

&lt;p&gt;Do not start by exposing the full API.&lt;/p&gt;

&lt;p&gt;Start with one workflow and select the operations needed for that workflow. For the support example, the first MCP server might expose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;get_ticket&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create_ticket&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list_customer_tickets&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It probably should not expose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;delete_ticket&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bulk_export_tickets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;admin_reassign_all_tickets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;debug_ticket_index&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;login or token endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This selection is part of the product design. An AI client has to choose from the tools you expose. A smaller, clearer tool list is easier to use than a huge list of overlapping endpoints.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: turn each API operation into a clear MCP tool
&lt;/h2&gt;

&lt;p&gt;An MCP tool needs a name, description, and input schema. The API route is only the starting point.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;GET /tickets/{ticket_id}&lt;/code&gt;, a weak tool would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"getTicket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get ticket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The route is recognizable to a developer, but the AI client still has very little to work with.&lt;/p&gt;

&lt;p&gt;A better tool is more explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_ticket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Return the status, priority, requester, and latest update for one support ticket. Use this when the user already knows the ticket ID."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The ID of the support ticket to retrieve."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"include_comments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Whether to include recent ticket comments in the response."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the AI client enough information to choose the tool and build a valid call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: map parameters into one input schema
&lt;/h2&gt;

&lt;p&gt;HTTP APIs split inputs across different locations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;path parameters;&lt;/li&gt;
&lt;li&gt;query parameters;&lt;/li&gt;
&lt;li&gt;headers;&lt;/li&gt;
&lt;li&gt;request bodies;&lt;/li&gt;
&lt;li&gt;sometimes cookies or form fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An MCP tool should present the useful business inputs as one schema.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;GET /tickets/{ticket_id}?include_comments=true&lt;/code&gt;, the mapping is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ticket_id&lt;/code&gt; comes from the path;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;include_comments&lt;/code&gt; comes from the query string;&lt;/li&gt;
&lt;li&gt;the Bearer token comes from authentication, not the tool input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool input might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tck_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include_comments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter then constructs the API request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /tickets/tck_123?include_comments=true
Authorization: Bearer &amp;lt;token from runtime auth&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;POST /tickets&lt;/code&gt;, the request body becomes the tool input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cus_456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cannot access dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The user sees a 403 after logging in."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preserve API constraints when you build the schema. If &lt;code&gt;priority&lt;/code&gt; only accepts &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;normal&lt;/code&gt;, or &lt;code&gt;high&lt;/code&gt;, keep that enum. If &lt;code&gt;customer_id&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, and &lt;code&gt;description&lt;/code&gt; are required, mark them required. If the API expects a date in ISO format, say so.&lt;/p&gt;

&lt;p&gt;Bad schemas make the model guess. Good schemas reduce invalid calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: write descriptions that help tool selection
&lt;/h2&gt;

&lt;p&gt;Descriptions are not decoration. They affect which tool the AI client chooses.&lt;/p&gt;

&lt;p&gt;For similar tools, the description should explain the difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list_customer_tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"List support tickets for one customer. Use this when the user knows the customer ID and wants to review several tickets. For one known ticket ID, use get_ticket."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare that with the vague version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list_customer_tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lists tickets."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful tool description often includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the tool does;&lt;/li&gt;
&lt;li&gt;when to use it;&lt;/li&gt;
&lt;li&gt;what input must already be known;&lt;/li&gt;
&lt;li&gt;whether it reads or changes data;&lt;/li&gt;
&lt;li&gt;what it returns;&lt;/li&gt;
&lt;li&gt;when another tool is a better fit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API endpoint tells you what route to call. The tool description tells the AI client why and when to call it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: keep authentication out of normal tool inputs
&lt;/h2&gt;

&lt;p&gt;Your API might use API keys, Bearer tokens, or OAuth.&lt;/p&gt;

&lt;p&gt;Those credentials should not become ordinary tool arguments like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tck_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secret-key-here"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a bad pattern. It makes credentials model-visible and easy to leak into prompts, logs, examples, or retries.&lt;/p&gt;

&lt;p&gt;Instead, keep the tool input focused on the business operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tck_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include_comments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP server should receive or access credentials through the runtime authentication path, then forward the credential to the original API.&lt;/p&gt;

&lt;p&gt;The API still owns authorization. It should check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which user or service identity is calling;&lt;/li&gt;
&lt;li&gt;which tenant or workspace the caller belongs to;&lt;/li&gt;
&lt;li&gt;which role or scope the caller has;&lt;/li&gt;
&lt;li&gt;whether the caller can access the requested record;&lt;/li&gt;
&lt;li&gt;whether the caller can perform the requested action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The MCP layer should not become a shortcut around your API's permission model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: implement the tool handler
&lt;/h2&gt;

&lt;p&gt;The tool handler connects the MCP call to the API request.&lt;/p&gt;

&lt;p&gt;Conceptually, a handler for &lt;code&gt;get_ticket&lt;/code&gt; does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTicketTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ticket_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;ticket_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;include_comments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/tickets/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ticket_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;include_comments&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include_comments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;include_comments&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mapApiResponseToToolResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the handler specific. The tool should call the known route for &lt;code&gt;get_ticket&lt;/code&gt;. Avoid a generic handler that accepts any path or method from the model.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;create_ticket&lt;/code&gt;, the handler would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;validate &lt;code&gt;customer_id&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, and &lt;code&gt;priority&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;create a JSON request body;&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;POST /tickets&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;forward the runtime credential;&lt;/li&gt;
&lt;li&gt;map the created ticket into a useful result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The handler should also treat failures deliberately. A &lt;code&gt;401&lt;/code&gt; should not look like "no tickets found." A validation error should tell the caller which input is wrong. A timeout should be visible as a timeout, not a generic failure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: map API responses into useful tool results
&lt;/h2&gt;

&lt;p&gt;Raw API responses are sometimes fine. But the tool result should help the AI client continue the workflow.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;get_ticket&lt;/code&gt;, the API might return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tck_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cannot access dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requester"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latest_update"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Customer sees a 403 after login."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP tool result should preserve the useful fields and avoid hiding the shape behind a vague string.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tck_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cannot access dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requester_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latest_update"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Customer sees a 403 after login."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not include secrets, internal headers, stack traces, or private debugging fields. If the API returns more data than the AI workflow needs, consider filtering or documenting the output carefully.&lt;/p&gt;

&lt;p&gt;For list operations, return pagination metadata when it matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tickets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tck_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cannot access dashboard"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_cursor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cursor_abc"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent can now explain the result and continue if the user asks for more.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: test the MCP tool path
&lt;/h2&gt;

&lt;p&gt;API tests usually start with a known route and a known payload. MCP testing should also check discovery and selection.&lt;/p&gt;

&lt;p&gt;Before production, test these cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the MCP client can connect to the server;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_ticket&lt;/code&gt; appears in the tool list;&lt;/li&gt;
&lt;li&gt;the description makes it clear when to use the tool;&lt;/li&gt;
&lt;li&gt;missing &lt;code&gt;ticket_id&lt;/code&gt; fails before the API request;&lt;/li&gt;
&lt;li&gt;invalid input types fail clearly;&lt;/li&gt;
&lt;li&gt;a valid ticket ID reaches the right API route;&lt;/li&gt;
&lt;li&gt;a missing ticket returns a clear not-found result;&lt;/li&gt;
&lt;li&gt;missing credentials produce an authentication error;&lt;/li&gt;
&lt;li&gt;insufficient permissions produce an authorization error;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;create_ticket&lt;/code&gt; works with the minimum valid body;&lt;/li&gt;
&lt;li&gt;invalid &lt;code&gt;priority&lt;/code&gt; values are rejected or clearly reported;&lt;/li&gt;
&lt;li&gt;timeouts and rate limits are visible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also test the agent workflow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;User: "Find ticket tck_123 and tell me whether it is still open."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The expected behavior is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the client chooses &lt;code&gt;get_ticket&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;it sends &lt;code&gt;ticket_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the API authenticates the request;&lt;/li&gt;
&lt;li&gt;the tool returns ticket status;&lt;/li&gt;
&lt;li&gt;the client answers based on the returned data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the agent chooses the wrong tool, the problem might be the name, description, or tool overload. If the tool call fails, the problem might be schema mapping, authentication, authorization, or the upstream API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9: decide where the MCP server runs
&lt;/h2&gt;

&lt;p&gt;For a prototype, a local server can be enough. For a SaaS product used by customers or remote AI clients, you need a hosted endpoint and an operational plan.&lt;/p&gt;

&lt;p&gt;For a self-hosted MCP server, you own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deployment;&lt;/li&gt;
&lt;li&gt;transport;&lt;/li&gt;
&lt;li&gt;TLS;&lt;/li&gt;
&lt;li&gt;authentication handling;&lt;/li&gt;
&lt;li&gt;secrets and credential rotation;&lt;/li&gt;
&lt;li&gt;timeouts and retries;&lt;/li&gt;
&lt;li&gt;logs;&lt;/li&gt;
&lt;li&gt;metrics;&lt;/li&gt;
&lt;li&gt;versioning;&lt;/li&gt;
&lt;li&gt;rollback;&lt;/li&gt;
&lt;li&gt;client compatibility testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a managed workflow, the goal is to avoid maintaining the MCP infrastructure yourself while still controlling which API capabilities are exposed.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://0mcp.io/blog/create-mcp-server-from-api?utm_source=devto" rel="noopener noreferrer"&gt;0mcp&lt;/a&gt;, the hosted workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;import a supported Swagger, OpenAPI, or Postman definition;&lt;/li&gt;
&lt;li&gt;review detected operations and validation feedback;&lt;/li&gt;
&lt;li&gt;select the API functions you want to expose;&lt;/li&gt;
&lt;li&gt;create or edit tools, resources, and prompts;&lt;/li&gt;
&lt;li&gt;use API key, Bearer token, or OAuth pass-through;&lt;/li&gt;
&lt;li&gt;test in the Playground;&lt;/li&gt;
&lt;li&gt;use the hosted Streamable HTTP endpoint from an MCP-compatible client;&lt;/li&gt;
&lt;li&gt;review logs, analytics, and configuration versions as the API changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;0mcp currently supports hosted Streamable HTTP servers, not local &lt;code&gt;stdio&lt;/code&gt; servers. Your original API remains responsible for business logic, authorization, pagination, rate limits, and data validation.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical checklist before launch
&lt;/h2&gt;

&lt;p&gt;Before you share the MCP endpoint, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The selected tools match real user workflows.&lt;/li&gt;
&lt;li&gt;Tool names are clear and stable.&lt;/li&gt;
&lt;li&gt;Descriptions explain when to use each tool.&lt;/li&gt;
&lt;li&gt;Path, query, and body parameters are mapped into the schema.&lt;/li&gt;
&lt;li&gt;Required fields, enums, defaults, and formats are accurate.&lt;/li&gt;
&lt;li&gt;Credentials are passed through runtime auth, not exposed as tool inputs.&lt;/li&gt;
&lt;li&gt;The upstream API enforces tenant, role, record, and action permissions.&lt;/li&gt;
&lt;li&gt;Valid, invalid, missing-auth, and forbidden cases have been tested.&lt;/li&gt;
&lt;li&gt;Tool results include enough data for the AI client to continue.&lt;/li&gt;
&lt;li&gt;Logs and analytics are available for debugging after launch.&lt;/li&gt;
&lt;li&gt;API changes have a versioning and rollback path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a tool fails several of these checks, fix the API contract or tool configuration before adding more capabilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Exposing every endpoint
&lt;/h3&gt;

&lt;p&gt;More tools can make selection harder. Start with a small workflow and add tools when tests or usage show a need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using vague tool names
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;api_request&lt;/code&gt; or &lt;code&gt;manage_ticket&lt;/code&gt; forces the AI client to infer too much. Prefer names like &lt;code&gt;get_ticket&lt;/code&gt;, &lt;code&gt;create_ticket&lt;/code&gt;, and &lt;code&gt;list_customer_tickets&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting credentials into the schema
&lt;/h3&gt;

&lt;p&gt;Keep API keys, Bearer tokens, and OAuth credentials out of tool inputs. Pass them through the runtime authentication path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ignoring response shape
&lt;/h3&gt;

&lt;p&gt;If the response is poorly documented, the agent may not know what it can safely say or do next. Keep response fields clear and predictable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing only happy paths
&lt;/h3&gt;

&lt;p&gt;Broken auth, invalid inputs, missing records, rate limits, and timeouts are part of production. Test them early.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Creating an MCP server from an API starts with careful mapping.&lt;/p&gt;

&lt;p&gt;The endpoint becomes a tool. Parameters become the input schema. Authentication becomes runtime credential handling. The response becomes the tool result. Errors become diagnosable failure paths.&lt;/p&gt;

&lt;p&gt;Once that mapping is clear, you can decide whether to build the server yourself or use a hosted workflow. If you want the detailed 0mcp version of this process, start with the guide on how to &lt;a href="https://0mcp.io/blog/create-mcp-server-from-api?utm_source=devto" rel="noopener noreferrer"&gt;create an MCP server from an API&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The n8n Community Node You Need Might Already Exist</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Wed, 26 Aug 2026 03:21:15 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/the-n8n-community-node-you-need-might-already-exist-2e4f</link>
      <guid>https://dev.to/bhavyshekhaliya/the-n8n-community-node-you-need-might-already-exist-2e4f</guid>
      <description>&lt;p&gt;You know that moment when you're building an n8n workflow and realize:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Wait… does n8n already have a node for this?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Maybe you need a specific AI provider.&lt;/p&gt;

&lt;p&gt;Or a browser automation tool.&lt;/p&gt;

&lt;p&gt;Or some obscure database.&lt;/p&gt;

&lt;p&gt;Or a service that isn't part of n8n's core integrations.&lt;/p&gt;

&lt;p&gt;The first instinct is usually to reach for the HTTP Request node.&lt;/p&gt;

&lt;p&gt;But before writing API calls yourself, there's another possibility:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Someone may have already built the node.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's one of the reasons I created &lt;strong&gt;Awesome n8n Community Nodes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The n8n ecosystem is bigger than it looks
&lt;/h2&gt;

&lt;p&gt;One of the best things about n8n is that it isn't limited to its built-in integrations.&lt;/p&gt;

&lt;p&gt;Developers can create community nodes and publish them as npm packages, extending n8n with new services, triggers, actions, AI capabilities, utilities, and more.&lt;/p&gt;

&lt;p&gt;The ecosystem has grown significantly. One existing ecosystem tracker had already indexed thousands of community nodes, showing just how quickly the space is expanding.&lt;/p&gt;

&lt;p&gt;That's great for n8n users.&lt;/p&gt;

&lt;p&gt;But it creates a new problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discovery.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Having thousands of nodes is useful only if you can actually find the one you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I built a directory
&lt;/h2&gt;

&lt;p&gt;I created:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Awesome n8n Community Nodes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/bhavyshekhaliya/awesome-n8n-community-nodes" rel="noopener noreferrer"&gt;https://github.com/bhavyshekhaliya/awesome-n8n-community-nodes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's an open-source, curated directory for discovering community-built n8n integrations and utilities.&lt;/p&gt;

&lt;p&gt;Instead of organizing everything as one massive list, I grouped nodes around what you're actually trying to automate.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 AI, Agents &amp;amp; Search
&lt;/h3&gt;

&lt;p&gt;Looking for AI, LLM, search, agent, or AI-media capabilities?&lt;/p&gt;

&lt;p&gt;There's a dedicated section for that.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Browser, Web &amp;amp; Scraping
&lt;/h3&gt;

&lt;p&gt;Need browser automation, crawling, scraping, or web extraction?&lt;/p&gt;

&lt;p&gt;You'll find those together.&lt;/p&gt;

&lt;h3&gt;
  
  
  💬 Communication &amp;amp; Messaging
&lt;/h3&gt;

&lt;p&gt;WhatsApp, email, chat, notifications, and other communication-related nodes have their own category.&lt;/p&gt;

&lt;h3&gt;
  
  
  🗄️ Data, Storage &amp;amp; Observability
&lt;/h3&gt;

&lt;p&gt;Database, storage, infrastructure, monitoring, and data-related integrations live here.&lt;/p&gt;

&lt;h3&gt;
  
  
  📄 Documents, Media &amp;amp; Productivity
&lt;/h3&gt;

&lt;p&gt;For document processing, media, transcription, content, and productivity workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 CRM &amp;amp; Sales
&lt;/h3&gt;

&lt;p&gt;Integrations that are useful for lead generation, sales, CRM, and customer workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛠️ Utilities
&lt;/h3&gt;

&lt;p&gt;And the smaller but extremely useful tools that don't fit neatly elsewhere.&lt;/p&gt;

&lt;p&gt;The repository currently links each entry to its npm package and, when available, its public source repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use npm search?
&lt;/h2&gt;

&lt;p&gt;You can.&lt;/p&gt;

&lt;p&gt;In fact, the repository points users toward npm's &lt;code&gt;n8n-community-node-package&lt;/code&gt; search as another way to explore the broader ecosystem.&lt;/p&gt;

&lt;p&gt;But package discovery and &lt;strong&gt;use-case discovery&lt;/strong&gt; are different things.&lt;/p&gt;

&lt;p&gt;If I search npm for a package, I need to already know roughly what I'm looking for.&lt;/p&gt;

&lt;p&gt;If I browse a categorized directory, I can discover things I didn't even know existed.&lt;/p&gt;

&lt;p&gt;That's the experience I wanted to improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small example
&lt;/h2&gt;

&lt;p&gt;Imagine you're building an AI workflow.&lt;/p&gt;

&lt;p&gt;You want to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search the web → extract information → process it with an LLM → send the result somewhere&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might start thinking about several APIs and custom HTTP Request nodes.&lt;/p&gt;

&lt;p&gt;But there may already be community nodes covering parts of that workflow.&lt;/p&gt;

&lt;p&gt;The directory makes it easier to explore those possibilities before rebuilding something yourself.&lt;/p&gt;

&lt;p&gt;That's the real value of a good ecosystem directory.&lt;/p&gt;

&lt;p&gt;It doesn't just help you find what you were searching for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It helps you discover what you didn't know you needed.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  This isn't a security audit
&lt;/h2&gt;

&lt;p&gt;There's an important distinction here.&lt;/p&gt;

&lt;p&gt;The repository is a &lt;strong&gt;curated directory&lt;/strong&gt;, not an endorsement or security audit.&lt;/p&gt;

&lt;p&gt;Community nodes run third-party code inside your n8n environment, so you should review the source, release history, permissions, dependencies, and license before installing one.&lt;/p&gt;

&lt;p&gt;Open source makes discovery easier.&lt;/p&gt;

&lt;p&gt;It doesn't remove the need for due diligence.&lt;/p&gt;

&lt;h2&gt;
  
  
  I want this to be community-built
&lt;/h2&gt;

&lt;p&gt;The directory is intentionally open source.&lt;/p&gt;

&lt;p&gt;If you maintain an n8n community node, you can contribute it.&lt;/p&gt;

&lt;p&gt;If you use a node that isn't listed, add it.&lt;/p&gt;

&lt;p&gt;If something is categorized incorrectly or outdated, open a PR or issue.&lt;/p&gt;

&lt;p&gt;The goal isn't to create another list that gets published once and forgotten.&lt;/p&gt;

&lt;p&gt;The goal is to make this a &lt;strong&gt;living map of the n8n community-node ecosystem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the more people contribute, the more useful that map becomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing
&lt;/h2&gt;

&lt;p&gt;I think we're going to see even more interesting n8n nodes as AI agents, MCP, browser automation, and specialized APIs become part of everyday automation workflows.&lt;/p&gt;

&lt;p&gt;That means the number of possible building blocks will keep increasing.&lt;/p&gt;

&lt;p&gt;And when the number of building blocks increases, &lt;strong&gt;discovery becomes infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's what I'm experimenting with here.&lt;/p&gt;

&lt;p&gt;If you're an n8n user, take a look:&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/bhavyshekhaliya/awesome-n8n-community-nodes" rel="noopener noreferrer"&gt;https://github.com/bhavyshekhaliya/awesome-n8n-community-nodes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you know a great community node that isn't there yet:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send it my way.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's build the directory together.&lt;/p&gt;

</description>
      <category>n8ncommunitynode</category>
      <category>n8nnodes</category>
      <category>opensource</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to Turn an OpenAPI Specification into MCP Tools</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Tue, 11 Aug 2026 04:57:13 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/how-to-turn-an-openapi-specification-into-mcp-tools-31k0</link>
      <guid>https://dev.to/bhavyshekhaliya/how-to-turn-an-openapi-specification-into-mcp-tools-31k0</guid>
      <description>&lt;p&gt;If you already maintain an API, you probably have most of the information needed to create an MCP interface.&lt;/p&gt;

&lt;p&gt;Your OpenAPI specification already describes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;available operations;&lt;/li&gt;
&lt;li&gt;paths and methods;&lt;/li&gt;
&lt;li&gt;required and optional parameters;&lt;/li&gt;
&lt;li&gt;request bodies;&lt;/li&gt;
&lt;li&gt;response formats; and&lt;/li&gt;
&lt;li&gt;authentication schemes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The work is not simply renaming an HTTP endpoint. An MCP tool needs to be understandable to an AI client: it needs a clear name, an accurate description, a useful input schema, and controlled access to the underlying API.&lt;/p&gt;

&lt;p&gt;This guide walks through that mapping with a small support-ticket API example.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAPI to MCP: the basic mapping
&lt;/h2&gt;

&lt;p&gt;An OpenAPI operation can provide the foundation for one MCP capability:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OpenAPI&lt;/th&gt;
&lt;th&gt;MCP tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;operationId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Starting point for the tool name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;summary&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Tool description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path parameters&lt;/td&gt;
&lt;td&gt;Usually required tool inputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query parameters&lt;/td&gt;
&lt;td&gt;Optional or required tool inputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request body schema&lt;/td&gt;
&lt;td&gt;Structured tool input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response schema&lt;/td&gt;
&lt;td&gt;Information about the returned result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security scheme&lt;/td&gt;
&lt;td&gt;Runtime authentication for the API request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selected operations&lt;/td&gt;
&lt;td&gt;The allowlist of capabilities exposed to the AI client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exact presentation can vary by implementation, but the important principle is stable: the tool should preserve the API's real contract instead of hiding it behind a vague "call endpoint" action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a useful OpenAPI operation
&lt;/h2&gt;

&lt;p&gt;Here is a shortened but concrete OpenAPI example for a support API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.3&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Support API&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.example.com&lt;/span&gt;

&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/tickets/{ticket_id}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;getTicket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get a support ticket&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Return the status, priority, requester, and latest update for one ticket.&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ticket_id&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;path&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;include_comments&lt;/span&gt;
          &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
            &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ticket returned&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Ticket"&lt;/span&gt;
      &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bearerAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="na"&gt;/tickets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;createTicket&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a support ticket&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a ticket for a customer issue.&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
              &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;title&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;description&lt;/span&gt;
              &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
                  &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;low&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;normal&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;high&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;201"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ticket created&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Ticket"&lt;/span&gt;
      &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bearerAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;

&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Ticket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;

  &lt;span class="na"&gt;securitySchemes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bearerAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
      &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bearer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example gives an MCP generator enough information to create two candidate tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getTicket&lt;/code&gt;, which needs &lt;code&gt;ticket_id&lt;/code&gt; and can optionally receive &lt;code&gt;include_comments&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;createTicket&lt;/code&gt;, which needs &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; and accepts a constrained &lt;code&gt;priority&lt;/code&gt; value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API remains responsible for business logic. MCP adds a structured interface through which an AI client can discover and call the selected capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Validate the specification before importing it
&lt;/h2&gt;

&lt;p&gt;Fix the API definition before you use it as the source for tools. At minimum, check that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every operation has a unique, meaningful identifier;&lt;/li&gt;
&lt;li&gt;summaries and descriptions explain what an operation does;&lt;/li&gt;
&lt;li&gt;parameter types match the values the API actually accepts;&lt;/li&gt;
&lt;li&gt;required fields are marked as required;&lt;/li&gt;
&lt;li&gt;request and response schemas match real JSON responses;&lt;/li&gt;
&lt;li&gt;authentication schemes are documented; and&lt;/li&gt;
&lt;li&gt;references such as &lt;code&gt;#/components/schemas/Ticket&lt;/code&gt; resolve correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An incomplete specification can still look valid to a human while producing confusing tools. A missing description, an incorrectly optional field, or a stale response schema gives the AI client the wrong information at the moment it chooses an action.&lt;/p&gt;

&lt;p&gt;0mcp supports Swagger 2.0, OpenAPI 3.0, OpenAPI 3.1, and Postman collections. For an OpenAPI workflow, import the specification rather than treating a raw REST base URL as the source. 0mcp validates the imported definition and shows warnings or errors before you publish the server.&lt;/p&gt;

&lt;p&gt;For more detail on the supported OpenAPI path, see the &lt;a href="https://docs.0mcp.io/learn/build/openapi-to-mcp-server" rel="noopener noreferrer"&gt;OpenAPI-to-MCP documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Choose the operations that should become tools
&lt;/h2&gt;

&lt;p&gt;Do not expose every endpoint just because it exists.&lt;/p&gt;

&lt;p&gt;Start with the smallest set that represents a useful workflow. For the support API above, that might be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;getTicket&lt;/code&gt; for reading the current state of a ticket;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;createTicket&lt;/code&gt; for opening a new issue; and&lt;/li&gt;
&lt;li&gt;a separate operation for adding an internal note, if that action is genuinely needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This selection is an access-control decision as well as a usability decision. Internal administration endpoints, destructive actions, debugging routes, and duplicate operations should not automatically become AI capabilities.&lt;/p&gt;

&lt;p&gt;HTTP methods are useful clues, but they do not decide the tool boundary by themselves. A &lt;code&gt;GET&lt;/code&gt; operation might provide data for a tool or resource, while a &lt;code&gt;POST&lt;/code&gt; operation might represent a state-changing tool. Consider what the capability means to the user, what permissions it needs, and whether an AI client can use it safely.&lt;/p&gt;

&lt;p&gt;In 0mcp, you can review the detected operations and select which API functions to expose. You can also create or update tools, resources, and prompts as the integration becomes more deliberate.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Map parameters into one tool schema
&lt;/h2&gt;

&lt;p&gt;HTTP APIs distribute inputs across several locations. An MCP tool presents the inputs as one structured schema.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;getTicket&lt;/code&gt; operation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ticket_id&lt;/code&gt; comes from the path and is required;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;include_comments&lt;/code&gt; comes from the query string and is optional;&lt;/li&gt;
&lt;li&gt;the bearer credential is used for authentication, not exposed as a normal tool argument.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A conceptual MCP tool schema could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get_ticket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Return the status, priority, requester, and latest update for one support ticket."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The ID of the ticket to retrieve."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"include_comments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Whether to include ticket comments."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ticket_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;createTicket&lt;/code&gt;, the request body becomes a structured input with required &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; fields. The &lt;code&gt;priority&lt;/code&gt; enum should stay an enum. Preserving constraints helps the AI client form a valid request instead of guessing at allowed values.&lt;/p&gt;

&lt;p&gt;When a path parameter and a body field have the same name, resolve the collision deliberately. When a schema is reused through &lt;code&gt;$ref&lt;/code&gt;, make sure the generated tool still presents the fields and descriptions the client needs. When an API uses pagination, document the cursor or page inputs clearly; the API owner remains responsible for pagination behavior and rate-limit handling.&lt;/p&gt;

&lt;p&gt;In 0mcp, tool names and descriptions can be edited in the dashboard. The underlying API schema should be corrected in the original OpenAPI definition so the API contract and the MCP interface do not drift apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Keep authentication out of the tool input
&lt;/h2&gt;

&lt;p&gt;The example uses a bearer security scheme. That tells the integration how the original API expects requests to be authenticated, but the token should not appear in a tool description, example payload, or ordinary user argument.&lt;/p&gt;

&lt;p&gt;0mcp supports API key, Bearer token, and OAuth authentication. Credentials are provided by the user through the MCP client at request time and passed through to the original API. 0mcp does not store those API keys, Bearer tokens, or OAuth credentials.&lt;/p&gt;

&lt;p&gt;You should still apply the same security practices you use for the API itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use least-privilege credentials;&lt;/li&gt;
&lt;li&gt;test with a staging account before exposing write operations;&lt;/li&gt;
&lt;li&gt;avoid placing secrets in the OpenAPI description;&lt;/li&gt;
&lt;li&gt;verify which operations each credential can access; and&lt;/li&gt;
&lt;li&gt;review error responses for accidental sensitive data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://0mcp.io/trust" rel="noopener noreferrer"&gt;0mcp Trust page&lt;/a&gt; explains the platform's credential pass-through and data-minimization approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Import, configure, and create the hosted server
&lt;/h2&gt;

&lt;p&gt;The managed workflow is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an 0mcp account.&lt;/li&gt;
&lt;li&gt;Import the OpenAPI specification.&lt;/li&gt;
&lt;li&gt;Review detected operations and any validation warnings.&lt;/li&gt;
&lt;li&gt;Select the operations to expose.&lt;/li&gt;
&lt;li&gt;Edit tool names and descriptions where the API wording is not clear enough for an AI client.&lt;/li&gt;
&lt;li&gt;Create the MCP server.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;0mcp hosts the resulting server and provides a Streamable HTTP endpoint. The default endpoint has this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yourservername.0mcp.dev/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint can be used by an MCP-compatible client. 0mcp does not require you to run a local &lt;code&gt;stdio&lt;/code&gt; server, and local/&lt;code&gt;stdio&lt;/code&gt; MCP servers are not currently supported by the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Test the tools before sharing the endpoint
&lt;/h2&gt;

&lt;p&gt;A successful import does not prove that the resulting tools are useful. Test the interface in the 0mcp Playground before connecting it to a production workflow. Inspect the available capabilities, call the tools with realistic inputs, verify authentication, and review the individual usage logs.&lt;/p&gt;

&lt;p&gt;For the support-ticket example, a useful test matrix looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;What to verify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;getTicket&lt;/code&gt; with a valid ID&lt;/td&gt;
&lt;td&gt;The path parameter is placed correctly and the returned JSON is understandable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;getTicket&lt;/code&gt; without &lt;code&gt;ticket_id&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The tool rejects an incomplete request before the API receives it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;getTicket&lt;/code&gt; with an unauthorized credential&lt;/td&gt;
&lt;td&gt;The authentication failure is visible and does not look like a successful empty result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;createTicket&lt;/code&gt; with the minimum valid body&lt;/td&gt;
&lt;td&gt;Required fields and the request body are mapped correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;createTicket&lt;/code&gt; with an invalid priority&lt;/td&gt;
&lt;td&gt;The enum constraint prevents or clearly reports an invalid value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A response containing pagination&lt;/td&gt;
&lt;td&gt;The tool description makes the next-page behavior clear&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Also test the failure paths that matter to your users: expired credentials, missing records, permission errors, API timeouts, and validation failures. A tool that only works on the happy path is not ready for an AI workflow.&lt;/p&gt;

&lt;p&gt;If you need a lower-level protocol inspection, the &lt;a href="https://docs.0mcp.io/learn/fundamentals/mcp-inspector-guide" rel="noopener noreferrer"&gt;MCP Inspector guide&lt;/a&gt; is a useful companion to application-level tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Plan for API changes
&lt;/h2&gt;

&lt;p&gt;The first tool call is only the start of the integration. APIs change, and those changes can affect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;required parameters;&lt;/li&gt;
&lt;li&gt;operation names and descriptions;&lt;/li&gt;
&lt;li&gt;authentication schemes;&lt;/li&gt;
&lt;li&gt;response shapes;&lt;/li&gt;
&lt;li&gt;pagination; and&lt;/li&gt;
&lt;li&gt;permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the API changes, update the source OpenAPI specification, review the affected operations, and test the tools again. In 0mcp, configuration versions let you save changes, review them, and restore an earlier configuration when needed. Saving an MCP configuration updates the hosted server without requiring a rebuild or changing its URL.&lt;/p&gt;

&lt;p&gt;Do not assume that an old MCP tool remains correct just because its name still exists. A schema change can turn a previously valid tool call into a bad request or cause the AI client to misunderstand the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  An operation did not become a tool
&lt;/h3&gt;

&lt;p&gt;Check the import warnings, the operation's HTTP method and path, whether the operation was selected, and whether required schema references resolve.&lt;/p&gt;

&lt;h3&gt;
  
  
  The tool has poor or generic descriptions
&lt;/h3&gt;

&lt;p&gt;Improve the OpenAPI &lt;code&gt;summary&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;operationId&lt;/code&gt;, parameter descriptions, and response documentation. AI clients rely on this text when deciding which capability to call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calls fail with authentication errors
&lt;/h3&gt;

&lt;p&gt;Compare the OpenAPI security scheme with the credential supplied at runtime. Check whether the API expects a bearer header, an API key in a specific location, or an OAuth flow. Do not solve the problem by putting the credential into the tool schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  The server exposes too many tools
&lt;/h3&gt;

&lt;p&gt;Reduce the selected operation set or separate unrelated product areas into different MCP servers. A large API surface is not automatically a useful AI interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  The response is not usable
&lt;/h3&gt;

&lt;p&gt;Check that the endpoint returns the JSON data your workflow needs. 0mcp currently focuses on JSON-based API responses; file uploads, file downloads, and binary API responses are not supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical launch checklist
&lt;/h2&gt;

&lt;p&gt;Before sharing an API-derived MCP server, confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the OpenAPI specification is valid and uses a supported version;&lt;/li&gt;
&lt;li&gt;operations have clear names and descriptions;&lt;/li&gt;
&lt;li&gt;only necessary capabilities are exposed;&lt;/li&gt;
&lt;li&gt;path, query, body, and response schemas match real API behavior;&lt;/li&gt;
&lt;li&gt;authentication is documented and credentials are passed at runtime;&lt;/li&gt;
&lt;li&gt;valid and invalid calls have been tested;&lt;/li&gt;
&lt;li&gt;pagination, rate limits, and permissions are understood; and&lt;/li&gt;
&lt;li&gt;a versioned update process exists for future API changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An OpenAPI specification is not an MCP server by itself. It is a strong source for building one when the contract is accurate and the exposed capability surface is intentional.&lt;/p&gt;

&lt;p&gt;If you want to take the hosted route, explore the &lt;a href="https://0mcp.io/api-to-mcp?utm_source=devto" rel="noopener noreferrer"&gt;0mcp API-to-MCP workflow&lt;/a&gt; or follow the &lt;a href="https://docs.0mcp.io/learn/build/openapi-to-mcp-server" rel="noopener noreferrer"&gt;OpenAPI-to-MCP documentation&lt;/a&gt; to review the implementation path.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>apitomcp</category>
      <category>openapi</category>
    </item>
    <item>
      <title>Is Every SaaS Company Going to Need an MCP Server?</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Sat, 01 Aug 2026 01:53:35 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/is-every-saas-company-going-to-need-an-mcp-server-1l79</link>
      <guid>https://dev.to/bhavyshekhaliya/is-every-saas-company-going-to-need-an-mcp-server-1l79</guid>
      <description>&lt;p&gt;Over the last year, I've noticed an interesting pattern.&lt;/p&gt;

&lt;p&gt;A few years ago, every software company wanted a REST API.&lt;/p&gt;

&lt;p&gt;Today, many of those same companies are asking a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"How do we make our product work with AI agents?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift made me wonder:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will having an MCP server eventually become as common as having an API?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm not convinced we're there yet.&lt;/p&gt;

&lt;p&gt;But I do think we're heading in that direction.&lt;/p&gt;

&lt;p&gt;Let's discuss why.&lt;/p&gt;




&lt;h2&gt;
  
  
  We've Seen This Story Before
&lt;/h2&gt;

&lt;p&gt;Think back to the early days of APIs.&lt;/p&gt;

&lt;p&gt;Many businesses didn't expose one publicly.&lt;/p&gt;

&lt;p&gt;Integrations were built manually.&lt;/p&gt;

&lt;p&gt;Partners requested CSV exports.&lt;/p&gt;

&lt;p&gt;Developers wrote custom scripts.&lt;/p&gt;

&lt;p&gt;Everything worked—but only until the next integration request arrived.&lt;/p&gt;

&lt;p&gt;Eventually, APIs became the standard because they reduced friction for everyone involved.&lt;/p&gt;

&lt;p&gt;Now AI is creating a similar moment.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI Doesn't Want Another Dashboard
&lt;/h2&gt;

&lt;p&gt;When people use tools like ChatGPT, Claude, Cursor, or other AI assistants, they don't want to switch between five browser tabs just to complete a task.&lt;/p&gt;

&lt;p&gt;Instead, they expect to say something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create a customer in our CRM."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Generate this invoice."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Show today's failed deployments."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For that to work, the AI needs a reliable way to interact with your product.&lt;/p&gt;

&lt;p&gt;That's where the conversation around MCP starts.&lt;/p&gt;




&lt;h2&gt;
  
  
  So... Why Can't AI Just Call My REST API?
&lt;/h2&gt;

&lt;p&gt;This is probably the most common question.&lt;/p&gt;

&lt;p&gt;Technically, AI &lt;em&gt;can&lt;/em&gt; call a REST API.&lt;/p&gt;

&lt;p&gt;But here's the problem.&lt;/p&gt;

&lt;p&gt;REST APIs were designed for software developers.&lt;/p&gt;

&lt;p&gt;Developers read documentation.&lt;/p&gt;

&lt;p&gt;Developers understand authentication.&lt;/p&gt;

&lt;p&gt;Developers know which endpoint to call first.&lt;/p&gt;

&lt;p&gt;Developers combine multiple requests together.&lt;/p&gt;

&lt;p&gt;AI agents don't "read documentation" the same way humans do.&lt;/p&gt;

&lt;p&gt;They need structured descriptions of available actions.&lt;/p&gt;

&lt;p&gt;They need context.&lt;/p&gt;

&lt;p&gt;They need discoverable tools.&lt;/p&gt;

&lt;p&gt;That's exactly what MCP provides.&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP Isn't Replacing APIs
&lt;/h2&gt;

&lt;p&gt;This is probably the biggest misconception I see online.&lt;/p&gt;

&lt;p&gt;Some people talk about MCP as if REST APIs are becoming obsolete.&lt;/p&gt;

&lt;p&gt;I don't think that's true.&lt;/p&gt;

&lt;p&gt;Your API is still the foundation.&lt;/p&gt;

&lt;p&gt;The MCP server simply becomes another interface for accessing it.&lt;/p&gt;

&lt;p&gt;A simple way to think about it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REST API → Built for applications and developers.&lt;/li&gt;
&lt;li&gt;MCP → Built for AI models and AI agents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One doesn't replace the other.&lt;/p&gt;

&lt;p&gt;They solve different problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes This Different From Previous Integrations?
&lt;/h2&gt;

&lt;p&gt;Historically, if you wanted your product to work with different platforms, you often built separate integrations.&lt;/p&gt;

&lt;p&gt;One for Slack.&lt;/p&gt;

&lt;p&gt;One for Zapier.&lt;/p&gt;

&lt;p&gt;One for Teams.&lt;/p&gt;

&lt;p&gt;One for internal automation.&lt;/p&gt;

&lt;p&gt;Now imagine doing the same thing for every AI platform that appears over the next few years.&lt;/p&gt;

&lt;p&gt;That doesn't seem sustainable.&lt;/p&gt;

&lt;p&gt;Standards become valuable when ecosystems grow.&lt;/p&gt;

&lt;p&gt;That's why MCP has attracted so much attention.&lt;/p&gt;




&lt;h2&gt;
  
  
  An Observation From Building Around MCP
&lt;/h2&gt;

&lt;p&gt;While working on &lt;strong&gt;0mcp&lt;/strong&gt; (&lt;a href="https://0mcp.io" rel="noopener noreferrer"&gt;https://0mcp.io&lt;/a&gt;), one thing became very clear.&lt;/p&gt;

&lt;p&gt;Most teams already have everything they need.&lt;/p&gt;

&lt;p&gt;They have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A REST API.&lt;/li&gt;
&lt;li&gt;An OpenAPI specification.&lt;/li&gt;
&lt;li&gt;Authentication.&lt;/li&gt;
&lt;li&gt;Documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The missing piece isn't the API.&lt;/p&gt;

&lt;p&gt;It's making that API understandable to AI systems.&lt;/p&gt;

&lt;p&gt;That's why 0mcp focuses on turning existing OpenAPI specifications into hosted MCP servers instead of asking teams to rebuild everything from scratch. If you're curious how the workflow looks, the documentation at &lt;strong&gt;&lt;a href="https://docs.0mcp.io" rel="noopener noreferrer"&gt;https://docs.0mcp.io&lt;/a&gt;&lt;/strong&gt; walks through the process from importing an API to exposing AI-ready tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Will Every Company Need One?
&lt;/h2&gt;

&lt;p&gt;Probably not.&lt;/p&gt;

&lt;p&gt;At least, not today.&lt;/p&gt;

&lt;p&gt;If your software has no need to interact with AI agents, adding an MCP server just because it's trendy doesn't make much sense.&lt;/p&gt;

&lt;p&gt;Technology should solve a real problem.&lt;/p&gt;

&lt;p&gt;But if your customers are already asking questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Can I use ChatGPT with your product?"&lt;/li&gt;
&lt;li&gt;"Can Claude perform actions in our account?"&lt;/li&gt;
&lt;li&gt;"Can Cursor access our internal APIs?"&lt;/li&gt;
&lt;li&gt;"Can we automate this using AI?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...then it's worth paying attention.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Think Happens Next
&lt;/h2&gt;

&lt;p&gt;I don't think every product will suddenly launch an MCP server next month.&lt;/p&gt;

&lt;p&gt;But I do think we'll see a gradual shift.&lt;/p&gt;

&lt;p&gt;Five years ago, companies proudly announced they had an API.&lt;/p&gt;

&lt;p&gt;Today, nobody makes that announcement because it's expected.&lt;/p&gt;

&lt;p&gt;I wouldn't be surprised if MCP follows a similar path.&lt;/p&gt;

&lt;p&gt;Not because it's fashionable.&lt;/p&gt;

&lt;p&gt;But because AI tools need a consistent way to interact with software.&lt;/p&gt;




&lt;h2&gt;
  
  
  If You're Building Today...
&lt;/h2&gt;

&lt;p&gt;I wouldn't recommend throwing away your existing APIs.&lt;/p&gt;

&lt;p&gt;Invest in them.&lt;/p&gt;

&lt;p&gt;Keep your OpenAPI specification up to date.&lt;/p&gt;

&lt;p&gt;Design clear endpoints.&lt;/p&gt;

&lt;p&gt;Good APIs become even more valuable in the AI era.&lt;/p&gt;

&lt;p&gt;When you're ready to expose those capabilities to AI clients, standards like MCP can build on top of the work you've already done rather than replacing it.&lt;/p&gt;

&lt;p&gt;That's one of the reasons we built &lt;strong&gt;0mcp&lt;/strong&gt;—to help teams reuse the APIs they already have instead of starting from zero. You can learn more on the homepage (&lt;a href="https://0mcp.io" rel="noopener noreferrer"&gt;https://0mcp.io&lt;/a&gt;) or explore the guides and examples in the documentation (&lt;a href="https://docs.0mcp.io" rel="noopener noreferrer"&gt;https://docs.0mcp.io&lt;/a&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  I'd Love to Hear Your Thoughts
&lt;/h2&gt;

&lt;p&gt;This is still an evolving space.&lt;/p&gt;

&lt;p&gt;Some teams are already exposing MCP servers.&lt;/p&gt;

&lt;p&gt;Others are experimenting.&lt;/p&gt;

&lt;p&gt;Many are still deciding whether they need one at all.&lt;/p&gt;

&lt;p&gt;So I'm curious:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do you think MCP servers will become as common as REST APIs, or will they remain a niche tool for AI-focused products?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's discuss in the comments.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Bhavy Shekhaliya</dc:creator>
      <pubDate>Wed, 29 Jul 2026 10:20:54 +0000</pubDate>
      <link>https://dev.to/bhavyshekhaliya/-4mhd</link>
      <guid>https://dev.to/bhavyshekhaliya/-4mhd</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bhavyshekhaliya/stop-building-custom-ai-integrations-use-mcp-instead-5d8l" class="crayons-story__hidden-navigation-link"&gt;Stop Building Custom AI Integrations. Use MCP Instead.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bhavyshekhaliya" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1638608%2Fbae60ede-857a-4050-9bbc-bc670f03506c.png" alt="bhavyshekhaliya profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bhavyshekhaliya" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Bhavy Shekhaliya
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Bhavy Shekhaliya
                
                
              
              &lt;div id="story-author-preview-content-4261790" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bhavyshekhaliya" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1638608%2Fbae60ede-857a-4050-9bbc-bc670f03506c.png" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Bhavy Shekhaliya&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bhavyshekhaliya/stop-building-custom-ai-integrations-use-mcp-instead-5d8l" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 29&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bhavyshekhaliya/stop-building-custom-ai-integrations-use-mcp-instead-5d8l" id="article-link-4261790"&gt;
          Stop Building Custom AI Integrations. Use MCP Instead.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/bhavyshekhaliya/stop-building-custom-ai-integrations-use-mcp-instead-5d8l" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/bhavyshekhaliya/stop-building-custom-ai-integrations-use-mcp-instead-5d8l#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
  </channel>
</rss>
