MewCP LogoAStheTech
MCPs
Use Cases

Use cases by category

Productivity & InboxInbox, calendar, and daily flowEngineering & DevOpsShip, debug, and run on-callSales & CRMPipeline, outreach, and dealsMarketing & GrowthCampaigns, SEO, and growthSupport & SuccessTriage tickets, keep customers happyFinance & OpsClose, reconcile, and expensesCreative & ContentGenerate assets and contentPeople & HiringHiring, onboarding, and HRResearch & DataSynthesize data and insights
See all use cases
Resources
BlogsProduct updates and storiesArticlesIntegration guides and code examples
PricingDocsSign in
Back to home
MewCP Logo

Infrastructure You Can Trust for Agentic Products

X

Categories

  • Productivity & Docs
  • Developer Tools
  • CRM & Sales
  • Finance & Commerce
  • Data & Analytics
  • Marketing & SEO
  • Search & Web
  • Communication
  • View All Servers →

Resources

  • Blog
  • Docs
  • Privacy Policy
  • Terms of Service

Blogs

  • View All Blogs →

Articles

  • View All Articles →
Browse Servers|Pricing|Contact

Browse by Category

Productivity & Docs

  • Gmail
  • Google Drive
  • YouTube
  • Google Calendar
  • Google People
  • Google Classroom
  • Notion
  • ClickUp
  • Figma
  • Google Tasks
  • Cal
  • Monday
  • Luma
  • Notion MCP
  • Mem MCP
  • Linear MCP
  • Calendly MCP
  • Consensus MCP
  • Craft MCP
  • Close MCP
+ 34 more
  • Dice MCP
  • Lumin PDF MCP
  • Develop21 MCP
  • Granola MCP
  • Lucid MCP
  • Mermaid Chart MCP
  • Fireflies MCP
  • ClickUp MCP
  • Miro MCP
  • Llamaindex MCP
  • Otter MCP
  • Mobbin MCP
  • Descript MCP
  • ToDoist MCP
  • SlidesGPT MCP
  • AgileHero MCP
  • JobYap MCP
  • AccountHub MCP
  • Slicktrip MCP
  • Gemina MCP
  • QrVerloz
  • Tempreon MCP
  • HyperFrames MCP
  • Menti MCP
  • Tally MCP
  • Taplio MCP
  • TickTick MCP
  • Motion MCP
  • StockCake MCP
  • Dashly MCP
  • DoingWork MCP
  • 1Stay Hotel Booking
  • 42min MCP
  • MyLeadTech MCP

Developer Tools

  • Gemini
  • Veo
  • ClickUp
  • Firecrawl
  • Vercel
  • Apify
  • Github
  • Chef
  • Scientific Calculator
  • Figma
  • HTTP
  • Perplexity
  • Apify MCP
  • Hugging Face Hub MCP
  • Buildkite MCP
  • Cloudflare MCP
  • Context7 MCP
  • Ahrefs MCP
  • Sentry MCP
  • Brevo Docs MCP
+ 57 more
  • X Docs MCP
  • Jev
  • Linear MCP
  • Calendly MCP
  • Craft MCP
  • DeepWiki MCP
  • Inspo MCP
  • Kernel MCP
  • Malwarebytes MCP
  • Mermaid Chart MCP
  • Supabase MCP
  • Microsoft Learn MCP
  • Webflow MCP
  • Scalar Docs MCP
  • Oneuptime MCP
  • Redocly MCP
  • Reducto Docs MCP
  • Llamaindex Docs MCP
  • B12 MCP
  • Lucid Docs MCP
  • Airwallex Docs MCP
  • Langfuse Docs MCP
  • Glen Docs MCP
  • AgentMail
  • Gogs Docs MCP
  • Netlify MCP
  • Neon MCP
  • Minlify Admin MCP
  • Mintlify Index MCP
  • Fern Docs MCP
  • Greptile MCP
  • Statssif Docs MCP
  • Scorecard MCP
  • Railway MCP
  • Inkbox AI
  • Stele MCP
  • LastPing MCP
  • ConsentStack
  • Iubenda MCP
  • BSV.CX MCP
  • Agent Traffic Lab MCP
  • Nippy MCP
  • Just Domain MCP
  • Grafana Cloud MCP
  • HotAPI Docs
  • Endoflife.ai MCP
  • ElevenLabs MCP
  • Laso Docs MCP
  • OpenRouter MCP
  • Replit MCP
  • Tripadvisor Docs MCP
  • GitLab MCP
  • Mailchimp Docs MCP
  • Clerk MCP
  • HotAPI
  • TinyFish
  • Porter MCP

CRM & Sales

  • Google People
  • OneSignal MCP
  • Brevo Docs MCP
  • Brevo
  • Brevo MCP
  • Carbon Voice MCP
  • Clay MCP
  • Close MCP
  • Attio MCP
  • Clarify MCP
  • Hunter.io
  • Plain MCP
  • Modem MCP
  • Whats MCP
  • HubSpot MCP
  • Getleads.io MCP

Finance & Commerce

  • Kite
  • Razorpay
  • Polymarket
  • Stripe
  • Binance
  • Upstox
  • Aiwyn MCP
  • Era-Context-MCP
  • Granted MCP
  • XDC AI MCP
  • Agentery MCP
  • Agent Embassy
  • Quick Commerce MCP
  • Longbridge MCP
  • Mercury MCP
  • Blockscout MCP
  • Octagon AI MCP
  • Stripe MCP
  • Taskrabbit MCP
  • SeatGeek MCP
+ 17 more
  • Clarity AI MCP
  • Beyond Payday MCP
  • DeFade MCP
  • MobileMRR MCP
  • SecondAppraisal MCP
  • Brek MCP
  • Coinversa Pulse MCp
  • Supermetrics Marketing Analytics MCP
  • DeepLedger MCP
  • Laso MCP
  • Laso Docs MCP
  • Razorpay MCP
  • Kiwi MCP
  • lastminute mcp
  • Coinfuty MCP
  • Pyth MCP
  • Carta MCP

Data & Analytics

  • Apify MCP
  • Cloudflare MCP
  • Ahrefs MCP
  • Candid MCP
  • Consensus MCP
  • Contentsquare MCP
  • Era-Context-MCP
  • Instinct MCP
  • legal Data Hunter MCP
  • Marcopolo MCP
  • Mixpanel MCP
  • MOSPI MCP
  • Hex MCP
  • OpenRevenue MCP
  • Statsig MCP
  • Synthesize Bio MCP
  • PostHog MCP
  • PopHIVE MCP
  • Fuse Health MCP
  • Cliometry MCP
+ 6 more
  • AccuWeather MCP
  • ChEMBL MCP
  • ClinicalTrials.gov MCP
  • Staats MCP
  • NPI Registry MCP
  • AOI MCP

Marketing & SEO

  • YouTube
  • Google Business
  • Mailchimp
  • Google Search Console
  • OneSignal MCP
  • Cloudflare MCP
  • Brevo Docs MCP
  • Brevo
  • Brevo MCP
  • AirOps MCP
  • Clay MCP
  • Contentsquare MCP
  • Reelsmith MCP
  • GoDaddy MCP
  • Metricool MCP
  • Webflow MCP
  • Windsor MCP
  • Commonroom MCP
  • B12 MCP
  • Hunter.io
+ 19 more
  • Get MCP Ads
  • Minlify Admin MCP
  • Zernio MCP
  • Affiliatespy MCP
  • VarynForge
  • ListingGood MCP
  • North Noir MCP
  • Supermetrics Marketing Analytics MCP
  • NitroSend MCP
  • Spicy API MCP
  • Cromanion MCP
  • Manifold MCP
  • Cloro MCP
  • Agency AI MCP
  • Taplio MCP
  • Moda MCP
  • Semrush MCP
  • Mangools
  • Runway MCP

Search & Web

  • Web Scrapper
  • Firecrawl
  • Apify
  • Perplexity
  • Context.dev
  • Exa
  • Brave Search
  • Apify MCP
  • Ahrefs MCP
  • DeepWiki MCP
  • Dice MCP
  • GoDaddy MCP
  • Granted MCP
  • Microsoft Learn MCP
  • Viator MCp
  • Scholargateway MCP
  • Parallel MCP
  • Mintlify Index MCP
  • OpenWeather MCP
  • Simplescraper MCP
+ 24 more
  • SearchApi MCP
  • PubMed MCP
  • YouTube Transcript AI MCP
  • AccuWeather MCP
  • bioRxiv MCP
  • HEY Research Lab MCP
  • CourtListener MCP
  • Elicit MCP
  • StockCake MCP
  • TradesWithAI MCP
  • GSCmate MCP
  • Superfast Robotics MCP
  • Loppee MCP
  • Energy North Journal UK MCP
  • SocialToAI MCP
  • Agent Evidence API MCP
  • Permit Scouter
  • MyStreetData USA
  • Shop Nobi MCP
  • EverythingMeetsAI MCP
  • Mubboo Flights Search MCP
  • Ticketglide MCP
  • Premium Cruise Fares MCP
  • Tripadvisor MCP

Communication

  • Gmail
  • Google Meet
  • Google Calendar
  • Mailchimp
  • WhatsApp
  • Slack
  • OneSignal MCP
  • Brevo Docs MCP
  • Carbon Voice MCP
  • Hunter.io
  • Outlook
  • Nylas MCP
  • Resend MCP
  • Read AI MCP
  • Whats MCP
  • Inkbox AI
  • QrVerloz
  • ElevenLabs MCP
  • Fathom MCP
  • MyBestSim MCP
+ 4 more
  • Redra MCP
  • 1Stay Hotel Booking
  • ActivitySmith MCP
  • Runway MCP

© 2026 MewCP. All rights reserved.

  1. Home
  2. Blogs
  3. MCP vs API: What MCP Actually Adds On Top Of Your API

MCP vs API: What MCP Actually Adds On Top Of Your API

by Rohit Gite, Founder CTO @MewCP·September 6, 2026·14 min read

Nobody is deprecating REST. An MCP server is usually just another client of the API you already shipped. Here is the call path, the wrapper code, and the token rule that bites teams.

MCP vs API: What MCP Actually Adds On Top Of Your API

Day 16 ended on one line: MCP standardizes the connection, not the tool. The question that follows within about ten seconds is whether that means the REST API you shipped last year is now legacy. It is not. MCP vs API is a layering question, and once you see where the boundary sits the rest of the decision gets easy. Your API keeps every job it has today, and the MCP server you write will spend most of its life as a client of that API.

The misreading is expensive in a specific way. A team hears "standard protocol for AI" and schedules a rewrite of integrations that were already correct. Two weeks later the API underneath is byte for byte identical, the agent still calls the same endpoints, and the only thing that changed is who calls them and how they were described. The rewrite was never the work.

Here is the part that usually unlocks it. MCP is itself an API. It is a JSON-RPC 2.0 API with a fixed method set, a fixed result envelope, and a defined authorization story. So the comparison is not API versus something that is not an API. It is a general purpose interface you designed for developers against one specific standardized interface designed for a model to consume at runtime.

MCP vs API: two questions, two layers

An API answers: how do two applications call each other? MCP answers: how does a model discover, understand and safely invoke a tool it has never seen before?

Your APIMCP
Primary consumerA developer who read your docsA model choosing at runtime
Contract lives inAn OpenAPI file, a README, an SDKA tools/list response on the wire
When the client learns the shapeAt the time somebody wrote the codeAt the time it connects
Surface design goalCover every operation completelyCover the tasks a model should perform
AuthWhatever you choseOAuth 2.1 resource server on HTTP transports
ErrorsYour status codes and body shapesJSON-RPC errors plus isError: true results

The clean test is who writes the integration. If a human reads your docs and writes the calling code ahead of time, an API is sufficient and MCP is overhead. If a model has to pick a call at runtime, from a set of capabilities that did not exist when its host application shipped, you need something that describes itself. That is the whole job MCP took.

What your API still owns

MCP takes none of the following, and any explanation that suggests otherwise is wrong:

  • Endpoints, methods, paths, versioning
  • Data contracts, validation, serialization
  • Business logic and permission checks
  • Rate limits, quotas, metering, billing
  • Your database, your queues, your background jobs

If your API is the system of record today, it stays the system of record. MCP sits above it and describes a subset of it in a shape a model can use.

The call path, drawn properly

Blueprint diagram of an agent and MCP client on the left flowing into an amber MCP server block, which fans out over HTTPS and SQL to a REST API, a database and a SaaS app

Left to right: the agent decides, the MCP client speaks the protocol, the MCP server receives JSON-RPC, and then the server does ordinary backend work. It issues an HTTPS request to your REST API, or runs a SQL query, or calls a third party SaaS API. Nothing in that last hop is special. Your API sees a normal service client with a normal credential and a normal user agent. It never learns a model is on the other end, and it should not have to.

On Streamable HTTP the inbound call is one HTTP POST per JSON-RPC message. The 2026-07-28 revision mirrors two body fields into headers so gateways can route and meter without parsing the body:

POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: find_order
 
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "find_order",
    "arguments": { "reference": "ORD-10432" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

Mcp-Method is required on all requests and Mcp-Name is required on tools/call, resources/read and prompts/get. If a header disagrees with the body, the server must reject with 400 Bad Request and JSON-RPC error code -32020, HeaderMismatch. That rule exists because a load balancer routing on a header while the server executes on the body is a real security hole.

The practical payoff is that you can rate limit refund_order differently from find_order at the edge, without your gateway ever reading a JSON body. That is something a plain REST API gives you for free through the path, and it is something MCP had to add deliberately because every call goes to the same endpoint.

A server that wraps the REST API you already have

Install the official Python SDK. Version 2.x renamed the in-SDK FastMCP class to MCPServer and dropped the old mcp.server.fastmcp import path, so older snippets will not run.

uv add "mcp[cli]" httpx

Now server.py. This wraps an existing orders REST API. Read it for two things: how little of it is protocol code, and where the credentials come from.

import os
from typing import Annotated, Literal
 
import httpx
from pydantic import Field
 
from mcp.server import MCPServer
from mcp.server.auth.middleware.auth_context import get_access_token
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
from mcp.server.mcpserver.exceptions import ToolError
from mcp.types import ToolAnnotations
 
ORDERS_API = os.environ["ORDERS_API_BASE_URL"]
RESOURCE_URL = "https://mcp.example.com/mcp"
 
 
class IntrospectingVerifier(TokenVerifier):
    """Validate the inbound bearer token, and check who it was minted for."""
 
    def __init__(self, introspection_url: str, client_id: str, client_secret: str) -> None:
        self.introspection_url = introspection_url
        self.auth = (client_id, client_secret)
 
    async def verify_token(self, token: str) -> AccessToken | None:
        async with httpx.AsyncClient(timeout=5.0) as http:
            response = await http.post(
                self.introspection_url, data={"token": token}, auth=self.auth
            )
        if response.status_code != 200:
            return None
 
        claims = response.json()
        if not claims.get("active"):
            return None
 
        # RFC 8707 audience binding. A token that is perfectly valid somewhere
        # else is not valid here. Skipping this check is the confused deputy bug.
        aud = claims.get("aud")
        audiences = aud if isinstance(aud, list) else [aud]
        if RESOURCE_URL not in audiences:
            return None
 
        return AccessToken(
            token=token,
            client_id=claims.get("client_id", ""),
            scopes=claims.get("scope", "").split(),
            expires_at=claims.get("exp"),
            resource=RESOURCE_URL,
            subject=claims.get("sub"),
        )
 
 
mcp = MCPServer(
    "Orders",
    instructions="Look an order up before answering anything about it.",
    token_verifier=IntrospectingVerifier(
        introspection_url="https://auth.example.com/oauth/introspect",
        client_id=os.environ["INTROSPECTION_CLIENT_ID"],
        client_secret=os.environ["INTROSPECTION_CLIENT_SECRET"],
    ),
    auth=AuthSettings(
        issuer_url="https://auth.example.com",
        resource_server_url=RESOURCE_URL,
        required_scopes=["orders:read"],
    ),
)
 
 
def require_caller() -> AccessToken:
    caller = get_access_token()
    if caller is None:
        raise ToolError("This server requires an authenticated caller.")
    return caller
 
 
def api_client(caller: AccessToken) -> httpx.AsyncClient:
    """One client per call. The caller's MCP token never leaves this process."""
    return httpx.AsyncClient(
        base_url=ORDERS_API,
        headers={
            "Authorization": f"Bearer {os.environ['ORDERS_API_SERVICE_TOKEN']}",
            "X-Acting-Subject": caller.subject or "",
        },
        timeout=15.0,
    )
 
 
@mcp.tool(
    title="Find an order",
    annotations=ToolAnnotations(read_only_hint=True, open_world_hint=False),
)
async def find_order(
    reference: Annotated[
        str,
        Field(description="Order number like ORD-10432, or the customer's email address."),
    ],
) -> str:
    """Find a single order by order number or customer email.
 
    Use this before answering anything about an order's status, contents or
    refund eligibility. Returns one order with its line items. If the customer
    cannot give you a reference, use list_recent_orders instead.
    """
    caller = require_caller()
    async with api_client(caller) as api:
        response = await api.get("/v2/orders", params={"q": reference, "limit": 1})
 
    if response.status_code == 403:
        raise ToolError("This account is not allowed to read that order.")
    if response.status_code == 429:
        raise ToolError("The orders API is rate limiting us. Wait a few seconds, then retry.")
    response.raise_for_status()
 
    rows = response.json()["data"]
    if not rows:
        raise ToolError(
            f"No order matches {reference!r}. Try list_recent_orders with the customer's email."
        )
 
    order = rows[0]
    lines = [f"{order['reference']} | {order['status']} | placed {order['placed_at'][:10]}"]
    lines += [f"- {i['qty']} x {i['sku']} ({i['title']})" for i in order["items"]]
    lines.append(f"total {order['total']} {order['currency']}")
    return "\n".join(lines)
 
 
@mcp.tool(
    title="Refund an order",
    annotations=ToolAnnotations(
        read_only_hint=False, idempotent_hint=True, destructive_hint=False
    ),
)
async def refund_order(
    reference: Annotated[str, Field(description="Order number to refund, e.g. ORD-10432.")],
    reason: Annotated[
        Literal["damaged", "late", "wrong_item", "changed_mind"],
        Field(description="Why the refund is being issued."),
    ],
) -> str:
    """Issue a full refund against the original payment for one order.
 
    Only use this when the customer has explicitly asked for a refund and you
    have already confirmed the order with find_order. Safe to call twice: an
    order that is already refunded is reported, not refunded again.
    """
    caller = require_caller()
    if "orders:write" not in caller.scopes:
        raise ToolError(
            "This connection is read only. Ask the user to reconnect and grant refund access."
        )
 
    async with api_client(caller) as api:
        order = (await api.get(f"/v2/orders/{reference}")).json()
        if order["status"] == "refunded":
            return f"{reference} was already refunded on {order['refunded_at'][:10]}."
 
        payment = (await api.get("/v2/payments", params={"order": reference})).json()["data"][0]
        result = await api.post(
            "/v2/refunds",
            json={"payment_id": payment["id"], "amount": payment["amount"], "reason": reason},
            headers={"Idempotency-Key": f"refund:{reference}"},
        )
 
    if result.status_code == 409:
        return f"A refund for {reference} is already in progress."
    result.raise_for_status()
    return f"Refunded {payment['amount']} {payment['currency']} for {reference}. Reason: {reason}."
 
 
if __name__ == "__main__":
    mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)

Count the protocol code in there. Two decorators and a run() call. Everything else is HTTP calls into an API that already existed, error mapping, and a scope check. That is the honest shape of most production MCP servers, and it is why "MCP replaces REST" never survives contact with an actual implementation.

Two details worth pausing on.

ToolError produces a normal successful JSON-RPC result carrying isError: true and your message in the content. That is what you want, because the model reads it and can act on it. An unhandled exception gets the caller a generic "Error executing tool" and teaches the model nothing. Use raise_for_status() for genuine faults you do not want the model retrying around, and ToolError for anything it could plausibly recover from.

refund_order makes three calls into the orders API. It is still one tool. Which brings us to the design mistake that costs more than the protocol decision.

Endpoints are not tools

Blueprint comparison showing a tall column of eight small endpoint blocks on the left collapsing through a boundary into three wide amber task-shaped tool blocks on the right

The most common error is not choosing MCP over an API. It is mapping the API one to one onto tools. Forty endpoints become forty tools, the model gets forty descriptions competing for attention in one context window, and selection accuracy falls off a cliff. You have not standardized anything, you have pasted your OpenAPI file into the prompt with extra steps.

A tool is a task the model should be able to complete in one call. refund_order is a good tool even though it takes three internal API calls. patch_order_line_item_v2 is an endpoint wearing a tool costume.

Sizing rules that hold up:

  1. Name for intent, not for the route. find_order beats get_orders_query. The model is matching the user's goal against your name, not your resource hierarchy.
  2. Collapse the read variants. get_order_by_id, get_order_by_email and search_orders are one tool with one flexible argument. Three near-identical descriptions is three chances to pick wrong.
  3. Hide pagination. Take a limit, return a compact result, and say in the text when there is more. Do not make the model manage a cursor unless it genuinely has to.
  4. Compose writes. If completing a real user request takes four calls in a fixed order, that is one tool and the server does the orchestration.
  5. Read the whole surface out loud. If listing your tools takes longer than thirty seconds, the model has the same problem you do.

There is a related question about generating tools straight from an OpenAPI document. The generators are genuinely useful for a first pass, and for a large internal API they will save you a day. They degrade for predictable reasons: an operation summary was written for a developer who also has the guide open, there is no field for "when not to use this", every operation becomes a tool because the spec has no opinion about which ones matter, and deeply nested request schemas expand into hundreds of tokens per tool. Generate, then cut hard and rewrite the descriptions by hand. The generated version is a draft, not a surface.

The authorization boundary most teams get wrong

Blueprint diagram of two inbound token paths into an MCP server, an accepted token bound to this server in amber and a rejected token minted for another service struck through, with a separate outbound service credential to the REST API

This is where MCP genuinely does add something your API does not have, and where the spec is unusually strict.

On HTTP transports a protected MCP server acts as an OAuth 2.1 resource server. The 2026-07-28 revision requires that MCP servers implement OAuth 2.0 Protected Resource Metadata (RFC 9728) so clients can discover the right authorization server, and clients must use that metadata for discovery. Clients must send the RFC 8707 resource parameter on both the authorization request and the token request, naming the canonical URI of the server they intend to call.

Then the rule that catches people. The spec says MCP servers must validate that access tokens were issued specifically for them as the intended audience, that they must only accept tokens valid for their own resources, and that they must not accept or transit any other tokens. Clients, in turn, must not send a server any token other than one issued by that server's authorization server.

So token passthrough is not a shortcut you are choosing, it is a spec violation and a confused deputy vulnerability. If your MCP server accepts whatever bearer token arrives and forwards it to a downstream API, any service that can get a token from a shared authorization server can now drive your server's privileges. That is the check the IntrospectingVerifier above is doing when it compares aud against RESOURCE_URL, and it is four lines that a lot of servers are missing.

Which means your MCP server has two credential planes, not one:

  • Inbound. The caller's token, minted for your server, audience checked, scopes checked.
  • Outbound. Whatever your REST API, your database and your third party SaaS accounts need, resolved per call from the caller's identity.

Nothing about MCP solves the outbound plane. That is the same per-tenant credential problem from Day 15: encrypted storage, serialized refresh, rotation, revocation, audit. The protocol tells you who is calling. Acting as that person against Salesforce is still yours.

Two more things the spec fixed that are easy to miss. Servers should signal missing permission through the challenge rather than a bare failure: a 401 with a WWW-Authenticate header carrying resource_metadata, or a 403 with error="insufficient_scope" and the scope needed for this operation.

HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
                         scope="orders:write",
                         resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"

And on stdio, none of this applies. The spec says stdio implementations should not follow the HTTP authorization framework and should take credentials from the environment instead. That is reasonable when the server is a subprocess running as you. It is also exactly why the move from a local stdio server to a hosted HTTP one feels like a different project. It is a different project.

There is no session, so stop designing for one

If you learned MCP before 2026-07-28, unlearn the handshake. The initialize exchange is gone, the Mcp-Session-Id header is gone from Streamable HTTP, and every request carries its own protocol version, client info and client capabilities in _meta. Any instance can serve any request.

Two consequences that change how you wrap an API.

First, tools/list must not vary per connection. It may vary by the authorization presented on the request, which is the supported way to give a caller with orders:read a smaller tool surface than a caller with orders:write. Vary it by anything connection-scoped and you have broken a guarantee clients now cache against, via ttlMs and cacheScope on the list result. If your list depends on the caller's scopes, cacheScope is private.

Second, stateful workflows need explicit handles. If a tool starts something that later calls finish, return an opaque identifier and take it back as an ordinary argument. The spec's guidance is worth following exactly: validate the caller's authorization against the handle on every call rather than treating the handle as a capability, keep it opaque, give it a bounded lifetime, state that lifetime in the tool description, and return an expiry as a tool error the model can recover from.

Two kinds of error, and only one helps the model

MCP splits failure into two channels, and getting this backwards silently costs you recoveries.

Protocol errors are JSON-RPC error responses. Unknown tool, malformed request, server fault. Clients may show these to the model, but recovery is unlikely.

Tool execution errors are successful JSON-RPC results with isError: true and a readable message in content. API failures, validation problems, business logic refusals. Clients should hand these to the model, because that is what enables self correction.

Return a -32603 for "customer not found" and you have hidden the only useful information from the only participant that could act on it. Return it as a tool error saying "no order matches ORD-10432, try list_recent_orders with the customer's email" and the model does the right thing on the next turn. Error messages on an MCP server are prompts. Write them for the reader you actually have.

When you should not build an MCP server

Honest cases where a plain API wins:

  • One application, one integration, code you control on both ends. Call the API. MCP is a hop and a spec you do not need.
  • The call is deterministic and always happens at the same point in the flow. That is a function, not a tool.
  • Your latency budget is tight enough that an extra network hop matters.
  • The capability is one operation and will stay one operation.
  • You cannot yet answer "who is the caller" in production. Build identity first, then expose it.

Cases where it pays for itself:

  • More than one client, especially clients you do not control.
  • Tools change more often than client applications ship.
  • Users connect their own accounts, so the credential belongs to a person, not a machine.
  • You want the same capability available in Claude, in an IDE, and in your own agent runtime without writing it three times.

Most production teams end up shipping both, which is the whole point. The API is the system. The MCP server is a model-facing façade over part of it.

Before you put a wrapper server in front of a real API

  • Tools are named for tasks, not routes, and the whole surface reads in under thirty seconds
  • Every description says what it does, when to use it, when not to, and what comes back
  • The inbound token's audience is validated against this server's canonical URI, and no inbound token is ever forwarded downstream
  • Outbound credentials resolve per call from the caller's identity, never captured at import time
  • Missing permission returns a 403 with insufficient_scope and the exact scope needed
  • Recoverable failures raise a tool error with a message written for the model
  • Writes carry an idempotency key so a retry is not a second refund
  • Nothing depends on the connection, and any workflow state is an explicit handle
  • tools/list order is deterministic and cacheScope is honest about whether the list varies by authorization
  • The Streamable HTTP headers match the body, and you have tested that a mismatch returns -32020

Where this leaves you

MCP does not replace your API. It standardizes the layer where a model meets it, and it leaves the API itself completely alone. The work that shows up when you adopt it is not rewriting endpoints. It is designing a tool surface a model can navigate, holding two credential planes apart, and running the server so it stays up for callers you do not control.

That last part is what we build at MewCP: hosted MCP servers, a gateway in front so an agent sees the eight tools that matter instead of ninety, and multi-tenant credential handling so tenant A never acts as tenant B. The protocol is the easy half. The operational half is where the quarters go.

Next: what actually lives inside an MCP server, and why tools, resources and prompts are three different things on purpose.

ContentsSeptember 6, 2026
  1. MCP vs API: two questions, two layers
  2. What your API still owns
  3. The call path, drawn properly
  4. A server that wraps the REST API you already have
  5. Endpoints are not tools
  6. The authorization boundary most teams get wrong
  7. There is no session, so stop designing for one
  8. Two kinds of error, and only one helps the model
  9. When you should not build an MCP server
  10. Before you put a wrapper server in front of a real API
  11. Where this leaves you
Author

Rohit Gite, Founder CTO @MewCP

Share

Build with MewCP

Connect your AI agents to real tools in minutes.

Get started