FeedLaunchesDiscussionsOpportunitiesToolkits New Members
Back to blog
How to Create an MCP Server URL and Give AI Access to Your SaaS
Resources

How to Create an MCP Server URL and Give AI Access to Your SaaS

September 1, 2026 24 min read 0 views
Summarise this article in layman terms
If you don't have time, use this summarise option for fast clarity

Something changed in the last twelve months that most SaaS founders have not fully processed yet.

Anthropic launched MCP in November 2024. OpenAI adopted it in April 2025. Microsoft integrated it into Copilot Studio in July 2025. AWS added support in November 2025. By March 2026, all major AI providers were on board, with over 10,000 active public MCP servers and 97 million monthly SDK downloads across Python and TypeScript.

The implication for SaaS founders is direct: if your product does not have an MCP server, it is invisible to AI agents. Not harder to use. Invisible. An AI assistant that can call Notion, Jira, Slack, GitHub, and Stripe through MCP cannot do anything with your product unless you build the bridge.

This article is that bridge. It covers what MCP is, what an MCP server URL actually means, how to build and deploy one, how to secure it properly, and how to think about the distinction between public tools that anyone can access and private tools that require authentication. The code examples use TypeScript and Node.js throughout.

Before anything else, one important clarification that most MCP articles get wrong.

You do not simply generate a URL and AI gets access to your product. The URL is the endpoint. You still need to build or expose an MCP server, deploy it somewhere publicly reachable, configure the MCP endpoint, implement authentication for private tools, define the tools and resources your SaaS exposes, give the AI client the MCP endpoint URL, and test the full connection. This article walks through all of those steps.

What Is MCP and Why Does It Matter for SaaS

MCP standardises how AI models discover and use external tools, files, and data. It replaces bespoke, one-off integrations and solves the M×N problem. Without a shared protocol, connecting M AI applications to N tools requires up to M×N custom integrations. MCP reduces that to one integration per tool.

Before MCP, if you wanted your SaaS to work with Claude, ChatGPT, and Microsoft Copilot, you needed to build three separate integrations with three different APIs, authentication flows, and data formats. Now you build one MCP server and every AI that supports the protocol can connect to it.

An MCP server is a service that exposes your application's capabilities reading data, creating records, triggering actions to AI models through the Model Context Protocol.

The architecture has three layers:

  • MCP Host: The AI application the user interacts with (Claude, ChatGPT, Cursor, a custom agent)

  • MCP Server: Your code that translates between the MCP protocol and your SaaS backend

  • Your SaaS: The actual product, database, and API that contains the real data and functionality

The flow looks like this:

AI Client (Claude, ChatGPT, custom agent)
   ↓ MCP protocol
MCP Server (your code at https://api.yoursaas.com/mcp)
   ↓ authentication + API calls
Your SaaS Backend / API
   ↓
Real data and functionality

The AI client never talks directly to your database or backend API. It talks to your MCP server, which handles authentication, validates the request, calls your actual backend, and returns a structured response.

The Four Things You Need to Understand First

Before building, understand four distinctions that determine the architecture of your MCP server.

1. A Public MCP Documentation Page vs a Public MCP Server URL

These are different things.

A public MCP documentation page is a static webpage that tells developers and AI agents what tools your MCP server exposes, what parameters they accept, and what they return. It is documentation. No live connection happens when someone reads it.

A publicly reachable MCP server URL is a live endpoint that an AI client can connect to and actually call your tools through. This is what makes the magic happen.

Both matter but they serve different purposes. Build the server first. Document it after.

2. Public Tools vs Private Tools

Public tools expose data that does not require authentication. Your product directory, your public API documentation, your pricing information, your changelog. Any AI agent can connect and call these tools without providing credentials.

Private tools expose user-specific or account-specific data. A user's projects, their analytics, their billing information, their stored configurations. These require authentication. The AI client must present a valid credential before your MCP server will respond.

A well-designed MCP server often exposes both. Public tools encourage adoption and discoverability. Private tools deliver the actual value that makes users pay.

3. Transport: What Changed in 2025

Two transports are defined in the current MCP specification: stdio, where the server runs as a local subprocess and exchanges messages over standard input, and Streamable HTTP, introduced in the 2025-03-26 revision, which uses a single MCP endpoint that supports POST and GET, with optional Server-Sent Events streaming for server-to-client messages.

For a SaaS product exposing a public MCP endpoint, Streamable HTTP is the correct transport. The stdio transport is for local tools running on a user's machine, not for publicly accessible SaaS endpoints.

4. MCP and A2A Are Different Things

A frequently discussed companion to MCP is A2A (Agent-to-Agent), released by Google in April 2025. The two protocols are complementary, not competing. MCP defines how agents interact with tools, while A2A defines how agents collaborate with each other.

This article covers MCP only. A2A is a separate protocol for agent-to-agent coordination that is beyond the scope of building a single SaaS integration.

Step 1: Decide What Your SaaS Should Expose

The first architectural decision is what capabilities your MCP server will offer. Not everything in your product needs to be exposed. Start with the actions an AI agent would most frequently perform on behalf of a user.

Public tools (no authentication required):

  • Search your product catalog or directory

  • Read your public documentation

  • Get your pricing information

  • Retrieve your changelog

  • Search public community content

  • Get your API status

Private tools (authentication required):

  • Get a user's account information

  • Create a project or task

  • Retrieve analytics for a specific account

  • Search a user's stored data

  • Update records

  • Trigger account-specific actions

  • Read billing information

For this article, the example SaaS is a product analytics platform. The public tools expose aggregate product data. The private tools expose account-specific analytics.

Step 2: Build Your MCP Server

Install the MCP SDK and set up the server. The TypeScript SDK is the most widely used and best-documented option.

npm init -y
npm install @modelcontextprotocol/sdk express
npm install -D typescript @types/node @types/express ts-node

Create your TypeScript configuration:

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

The Basic MCP Server Structure

// src/server.ts
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

const app = express();
app.use(express.json());

function createMcpServer() {
  const server = new McpServer({
    name: "your-saas-mcp",
    version: "1.0.0",
  });

  // Tools are registered here
  registerPublicTools(server);
  registerPrivateTools(server);

  return server;
}

Registering Public Tools

Public tools require no authentication. Any AI client that connects to your endpoint can call these.

function registerPublicTools(server: McpServer) {
  // Tool 1: Search public product directory
  server.tool(
    "search_products",
    "Search the public product directory. No authentication required.",
    {
      query: z.string().describe("Search query"),
      limit: z.number().optional().default(10).describe("Number of results"),
    },
    async ({ query, limit }) => {
      // Call your public API endpoint
      const results = await searchPublicProducts(query, limit);

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(results, null, 2),
          },
        ],
      };
    }
  );

  // Tool 2: Get public documentation
  server.tool(
    "get_documentation",
    "Retrieve public documentation for a specific topic.",
    {
      topic: z.string().describe("Documentation topic to retrieve"),
    },
    async ({ topic }) => {
      const docs = await fetchPublicDocs(topic);

      return {
        content: [
          {
            type: "text",
            text: docs.content,
          },
        ],
      };
    }
  );
}

Registering Private Tools

Private tools check for a valid authentication token before doing anything else. If the token is missing or invalid, they return an error immediately.

function registerPrivateTools(server: McpServer) {
  // Tool: Get account analytics
  server.tool(
    "get_product_analytics",
    "Get analytics for your product. Requires authentication.",
    {
      product_id: z.string().describe("The product ID to get analytics for"),
      period: z
        .enum(["7d", "30d", "90d"])
        .default("7d")
        .describe("Time period for analytics"),
    },
    async ({ product_id, period }, context) => {
      // Extract auth token from request context
      const token = context.meta?.authToken as string | undefined;

      if (!token) {
        return {
          content: [
            {
              type: "text",
              text: "Authentication required. Please provide a valid API token.",
            },
          ],
          isError: true,
        };
      }

      // Validate the token against your auth system
      const user = await validateApiToken(token);

      if (!user) {
        return {
          content: [
            {
              type: "text",
              text: "Invalid or expired API token.",
            },
          ],
          isError: true,
        };
      }

      // Verify the user owns this product
      const hasAccess = await checkProductAccess(user.id, product_id);

      if (!hasAccess) {
        return {
          content: [
            {
              type: "text",
              text: "You do not have access to this product.",
            },
          ],
          isError: true,
        };
      }

      // Fetch and return the analytics
      const analytics = await fetchProductAnalytics(product_id, period);

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(analytics, null, 2),
          },
        ],
      };
    }
  );
}

Step 3: Set Up the HTTP Endpoint

The MCP server needs to be accessible over HTTP. The endpoint is what you give to AI clients as your MCP server URL.

// src/server.ts (continued)

// Handle MCP connections
app.post("/mcp", async (req, res) => {
  // Extract auth token from Authorization header if present
  const authHeader = req.headers.authorization;
  const authToken = authHeader?.startsWith("Bearer ")
    ? authHeader.slice(7)
    : undefined;

  const mcpServer = createMcpServer();

  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined, // stateless for 2026 spec compatibility
  });

  // Pass auth context to tools
  if (authToken) {
    req.headers["x-mcp-auth-token"] = authToken;
  }

  try {
    await mcpServer.connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error("MCP request failed:", error);
    res.status(500).json({ error: "Internal server error" });
  }
});

// Health check endpoint
app.get("/mcp/health", (req, res) => {
  res.json({ status: "ok", version: "1.0.0" });
});

// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`MCP server running at http://localhost:${PORT}/mcp`);
});

Your MCP endpoint is now at:

http://localhost:3000/mcp

When deployed, it becomes:

https://api.yoursaas.com/mcp

Step 4: Connect to Your SaaS Backend

Your MCP server should never access your production database directly. It should call your existing API layer, which handles database access, validation, and business logic.

// src/api-client.ts
// These functions call your existing SaaS API, not the database directly

const API_BASE = process.env.INTERNAL_API_URL || "https://api.yoursaas.com";
const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY!;

export async function searchPublicProducts(query: string, limit: number) {
  const response = await fetch(
    `${API_BASE}/v1/products/search?q=${encodeURIComponent(query)}&limit=${limit}`
  );

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  return response.json();
}

export async function fetchPublicDocs(topic: string) {
  const response = await fetch(
    `${API_BASE}/v1/docs/${encodeURIComponent(topic)}`
  );

  return response.json();
}

export async function validateApiToken(token: string) {
  const response = await fetch(`${API_BASE}/v1/auth/validate`, {
    headers: {
      Authorization: `Bearer ${token}`,
      "X-Internal-Key": INTERNAL_API_KEY,
    },
  });

  if (!response.ok) return null;
  return response.json();
}

export async function checkProductAccess(
  userId: string,
  productId: string
): Promise<boolean> {
  const response = await fetch(
    `${API_BASE}/v1/users/${userId}/products/${productId}/access`,
    {
      headers: { "X-Internal-Key": INTERNAL_API_KEY },
    }
  );

  return response.ok;
}

export async function fetchProductAnalytics(
  productId: string,
  period: string
) {
  const response = await fetch(
    `${API_BASE}/v1/products/${productId}/analytics?period=${period}`,
    {
      headers: { "X-Internal-Key": INTERNAL_API_KEY },
    }
  );

  return response.json();
}

This pattern keeps a clean separation between the MCP layer and your business logic. The MCP server handles protocol translation and authentication validation. Your existing API handles the actual data operations.

Step 5: Deploy the MCP Server

Your MCP server needs to be publicly accessible over HTTPS. An AI client in the cloud cannot connect to localhost:3000. You need a real public URL with a valid SSL certificate.

Option 1: Deploy Alongside Your Existing Backend

If you already have a Node.js backend deployed on a cloud provider, the simplest approach is adding the MCP routes to your existing Express or Fastify application.

// Add to your existing Express app
import { mcpRouter } from "./mcp/router";
app.use("/mcp", mcpRouter);

Your MCP endpoint becomes a path on your existing domain:

https://api.yoursaas.com/mcp

This is the cleanest option for most SaaS products. No additional infrastructure. No additional domain management. The MCP endpoint is just another route on your existing server.

Option 2: Serverless Functions

For products that want to keep the MCP layer separate or are already serverless:

Vercel:

// api/mcp.ts
import { createMcpServer } from "../../src/mcp-server";

export default async function handler(req: Request) {
  const mcpServer = createMcpServer();
  // Handle the MCP request
  return mcpServer.handleRequest(req);
}

Your endpoint becomes:

https://yourproject.vercel.app/api/mcp

Cloudflare Workers are also a strong option for MCP servers due to low latency globally and straightforward deployment. The Workers runtime supports the Streamable HTTP transport well.

Option 3: Dedicated Cloud Service

For teams that want the MCP infrastructure separate from their main application, a dedicated service on AWS Lambda, Google Cloud Run, or Azure Container Apps works well. This gives you independent scaling and deployment without touching your main application.

Getting the Public URL

After deployment, your MCP server URL follows this pattern:

https://[your-domain]/mcp

Examples:

https://api.founderstoday.com/mcp
https://mcp.yoursaas.com/mcp
https://yourproject.vercel.app/api/mcp

Test it is publicly reachable before giving it to an AI client:

curl https://api.yoursaas.com/mcp/health
# Should return: {"status":"ok","version":"1.0.0"}

Step 6: Secure Your MCP Server

Security is the section most MCP tutorials skip or undercover. An MCP server that is poorly secured is a significant risk. It is a publicly reachable endpoint that can call your backend systems.

Authentication: The Right Approach by Tool Type

Public tools: No authentication required. Any agent can call them. These should only access data you would put on a public webpage.

Private tools: Always require authentication. Return an error immediately if a valid credential is not present. Do not call your backend at all until authentication passes.

API Key Authentication (Simplest)

For most SaaS products starting with MCP, API key authentication is the right starting point. Simple to implement, simple for users to understand.

// middleware/auth.ts
export function extractApiKey(req: express.Request): string | null {
  // Accept token via Authorization header or query parameter
  const authHeader = req.headers.authorization;
  if (authHeader?.startsWith("Bearer ")) {
    return authHeader.slice(7);
  }

  // Fallback: accept via query param (less preferred)
  if (typeof req.query.api_key === "string") {
    return req.query.api_key;
  }

  return null;
}

export async function validateApiKey(key: string): Promise<User | null> {
  // Look up the key in your database
  // Return the associated user if valid, null if invalid or expired
  const user = await db.apiKeys.findOne({
    where: { key: hashApiKey(key), revoked: false },
    include: ["user"],
  });

  if (!user) return null;

  // Update last used timestamp
  await db.apiKeys.update(
    { lastUsedAt: new Date() },
    { where: { key: hashApiKey(key) } }
  );

  return user.user;
}

OAuth 2.0 (For User-Level Access)

The MCP specification supports OAuth for user authentication.</cite> For tools that act on behalf of a specific user rather than a service account, OAuth 2.0 is the right pattern.

The flow works like this:

  1. The AI client detects your MCP server requires OAuth

  2. It redirects the user to your authorization URL

  3. The user logs in and grants permission

  4. Your server issues an access token

  5. The AI client includes that token in subsequent MCP requests

Implementing full OAuth is beyond the scope of this article, but the MCP specification's OAuth support means AI clients that support the spec handle the flow automatically once your server is configured correctly.

Rate Limiting

Every public MCP endpoint needs rate limiting. Without it, a single misconfigured AI agent can exhaust your server capacity or your backend API quota.

import rateLimit from "express-rate-limit";

// Rate limit for unauthenticated (public tool) requests
const publicRateLimit = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 30, // 30 requests per minute per IP
  message: { error: "Too many requests. Please slow down." },
});

// Looser rate limit for authenticated requests
const authenticatedRateLimit = rateLimit({
  windowMs: 60 * 1000,
  max: 120, // 120 requests per minute for authenticated users
  keyGenerator: (req) => req.headers["x-user-id"] as string || req.ip!,
  message: { error: "Rate limit exceeded." },
});

app.post("/mcp", publicRateLimit, async (req, res) => {
  // Apply stricter or looser limits based on auth
  const token = extractApiKey(req);
  if (token) {
    // Apply authenticated rate limit
  }
  // Handle MCP request
});

Tool-Level Permission Scoping

Not all authenticated users should be able to call all private tools. Define explicit scopes for sensitive operations.

const TOOL_SCOPES: Record<string, string> = {
  get_product_analytics: "analytics:read",
  update_product: "products:write",
  delete_project: "projects:delete",
  get_billing: "billing:read",
};

async function checkToolPermission(
  user: User,
  toolName: string
): Promise<boolean> {
  const requiredScope = TOOL_SCOPES[toolName];
  if (!requiredScope) return true; // Public tool, no scope needed

  return user.scopes.includes(requiredScope);
}

What Never to Do

Never expose your database credentials through MCP. Your MCP server should call your API layer, not your database directly.

Never expose all records to all authenticated users. Just because a user authenticated does not mean they can see every other user's data. Always check ownership before returning data.

Never skip input validation. Validate every parameter before passing it to your backend. Zod, which you are already using for tool definitions, handles this well.

Never return stack traces or internal error details. Return generic error messages to the AI client. Log the details server-side.

Step 7: Give an AI Client Your MCP URL

Different AI clients connect to MCP servers in different ways. This is a real compatibility issue in 2026 that most MCP tutorials gloss over.

Claude Desktop

Add your MCP server to the Claude Desktop configuration file:

// claude_desktop_config.json
{
  "mcpServers": {
    "your-saas": {
      "url": "https://api.yoursaas.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY"
      }
    }
  }
}

Cursor

In Cursor, add the MCP server through Settings → MCP Servers:

{
  "mcpServers": {
    "your-saas": {
      "url": "https://api.yoursaas.com/mcp"
    }
  }
}

Custom AI Agents (via SDK)

When building a custom agent using the Anthropic SDK or OpenAI SDK, you configure the MCP connection programmatically:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// Using Claude with your MCP server
const response = await client.beta.messages.create({
  model: "claude-opus-4-5",
  max_tokens: 1024,
  messages: [
    {
      role: "user",
      content: "How many people viewed my product this week?",
    },
  ],
  tools: [
    {
      type: "mcp",
      server_url: "https://api.yoursaas.com/mcp",
      server_auth: {
        type: "bearer",
        token: userApiToken,
      },
    },
  ],
});

The important thing to know: not every AI client supports every connection method. Test your MCP server with the specific clients your users will use. What works in Claude Desktop may require different configuration in Cursor or a custom agent.

Step 8: Test the Full Connection

Testing happens in three layers. Test each one before moving to the next.

Layer 1: Test Your API Backend

Before testing the MCP layer, verify your backend API works correctly in isolation:

# Test public endpoint
curl https://api.yoursaas.com/v1/products/search?q=analytics

# Test authenticated endpoint
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://api.yoursaas.com/v1/products/prod_123/analytics?period=7d

If these do not work, the MCP layer will not work either.

Layer 2: Test the MCP Endpoint Directly

Use curl to send a raw MCP request to your endpoint and verify the response:

# Test that the endpoint is reachable
curl https://api.yoursaas.com/mcp/health

# Test a public tool call
curl -X POST https://api.yoursaas.com/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "search_products",
      "arguments": {
        "query": "analytics",
        "limit": 5
      }
    },
    "id": 1
  }'

# Test a private tool call with auth
curl -X POST https://api.yoursaas.com/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "get_product_analytics",
      "arguments": {
        "product_id": "prod_123",
        "period": "7d"
      }
    },
    "id": 2
  }'

Layer 3: Test End-to-End with an AI Client

Connect Claude Desktop or Cursor to your MCP server and ask a natural language question that should trigger one of your tools:

User: "How many people viewed my analytics dashboard this week?"

Expected flow:
  1. Claude recognizes this question requires the get_product_analytics tool
  2. Claude calls your MCP server with the appropriate parameters
  3. Your MCP server validates the auth token
  4. Your MCP server calls your backend API
  5. Your MCP server returns the analytics data
  6. Claude presents the data to the user in natural language

If the end-to-end test fails, work backwards through the layers: is the MCP endpoint reachable? Does the tool definition match what Claude expects? Is authentication being passed correctly? Is your backend API returning the right data?

Step 9: A Real Example, Turning a SaaS Feature Into an MCP Tool

Here is a complete, realistic example of one MCP tool from definition to response.

The feature: A product analytics view that shows how many users viewed a product in a given period.

The user's question to the AI: "How many people viewed my product this week?"

The MCP tool definition:

server.tool(
  "get_product_analytics",
  `Get view analytics for a specific product over a time period.
   Returns view count, unique visitors, and conversion rate.
   Requires authentication. Products must belong to the authenticated account.`,
  {
    product_id: z
      .string()
      .describe("The product ID (e.g., prod_abc123)"),
    period: z
      .enum(["7d", "30d", "90d"])
      .default("7d")
      .describe("Time period: 7d = last 7 days, 30d = last 30 days, 90d = last 90 days"),
  },
  async ({ product_id, period }, context) => {
    const token = context.meta?.authToken as string | undefined;

    // 1. Check authentication
    if (!token) {
      return {
        content: [{
          type: "text",
          text: "Authentication required. Add your API key to the MCP configuration.",
        }],
        isError: true,
      };
    }

    // 2. Validate token
    const user = await validateApiKey(token);
    if (!user) {
      return {
        content: [{
          type: "text",
          text: "Invalid API key. Please check your credentials.",
        }],
        isError: true,
      };
    }

    // 3. Check product ownership
    const hasAccess = await checkProductAccess(user.id, product_id);
    if (!hasAccess) {
      return {
        content: [{
          type: "text",
          text: `Product ${product_id} not found in your account.`,
        }],
        isError: true,
      };
    }

    // 4. Fetch analytics from your API
    const analytics = await fetchProductAnalytics(product_id, period);

    // 5. Return structured response
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          product_id,
          period,
          views: analytics.total_views,
          unique_visitors: analytics.unique_visitors,
          conversion_rate: `${analytics.conversion_rate}%`,
          period_label: period === "7d" ? "Last 7 days" : period === "30d" ? "Last 30 days" : "Last 90 days",
        }, null, 2),
      }],
    };
  }
);

The complete flow:

AI: "How many people viewed my product this week?"
  ↓
Claude identifies: get_product_analytics tool
  ↓
Claude calls: POST https://api.yoursaas.com/mcp
  Body: { method: "tools/call", params: { name: "get_product_analytics", arguments: { product_id: "prod_123", period: "7d" } } }
  Header: Authorization: Bearer user_api_key
  ↓
MCP Server: validates token, checks access, calls backend API
  ↓
Backend API: queries analytics database, returns data
  ↓
MCP Server: formats and returns structured response
  ↓
Claude: "Your product received 1,247 views from 892 unique visitors over the last 7 days, with a conversion rate of 3.2%."

Step 10: Common MCP Deployment Mistakes

These are the errors that show up most consistently when founders first deploy MCP servers.

Mistake 1: MCP endpoint is not publicly reachable The most common first deployment issue. Your MCP server is running locally or on an internal network that AI clients in the cloud cannot reach. Test the endpoint URL from a different network or use a tool like curl from a cloud shell before giving it to an AI client.

Mistake 2: Authentication is configured but not working Tokens are being passed but not extracted correctly from the request. Log the incoming headers in your MCP handler during development to verify what the AI client is actually sending.

Mistake 3: Tools expose too much data A tool that returns an entire user record when the AI only needed one field is a data minimization problem. Return only what is necessary for the task. If the AI asks for a user's email, return their email, not their entire profile object.

Mistake 4: MCP server directly accesses the production database Your MCP server should call your API layer, not connect to your database. A bug in your MCP tool definitions could otherwise result in unvalidated queries hitting your production database directly.

Mistake 5: No rate limiting A misconfigured AI agent in a loop can send hundreds of requests per minute. Rate limiting is not optional on a publicly reachable endpoint.

Mistake 6: Incorrect transport configuration Using the stdio transport for a server that needs to be publicly accessible over HTTP. Use Streamable HTTP for any server that an AI client will connect to over the internet.

Mistake 7: Assuming all AI clients handle authentication the same way Claude Desktop, Cursor, and custom agents all handle MCP authentication slightly differently. Test your server with each client type your users will actually use.

Mistake 8: Not validating tool input parameters Zod handles schema validation at the MCP layer but you should also validate at your API layer. Never trust that input arrived in the expected format even if your tool schema says it should.

Mistake 9: Missing error handling for backend failures If your backend API is down or returns an unexpected error, your MCP tool should return a graceful error message rather than crashing or returning a raw stack trace.

Mistake 10: Forgetting to handle the CORS headers If any web-based AI client needs to connect to your MCP endpoint, you need to configure CORS headers correctly. Most server-side AI clients do not have this issue but browser-based clients do.


Final Architecture Overview

Here is the complete architecture of a production-ready SaaS MCP integration:

┌─────────────────────────────────────────────────┐
│                  AI Client                       │
│  (Claude Desktop, Cursor, Custom Agent, etc.)    │
└─────────────────────┬───────────────────────────┘
                      │
                      │ HTTPS + MCP Protocol
                      │ Authorization: Bearer {token}
                      ▼
┌─────────────────────────────────────────────────┐
│              MCP Server Layer                    │
│  https://api.yoursaas.com/mcp                   │
│                                                  │
│  ┌─────────────────┐  ┌──────────────────────┐  │
│  │   Public Tools  │  │   Private Tools      │  │
│  │ (no auth)       │  │ (auth required)      │  │
│  │                 │  │                      │  │
│  │ search_products │  │ get_analytics        │  │
│  │ get_docs        │  │ create_project       │  │
│  │ get_pricing     │  │ update_record        │  │
│  └─────────────────┘  └──────────────────────┘  │
│                                                  │
│  ┌─────────────────────────────────────────────┐ │
│  │         Security Layer                      │ │
│  │  Rate Limiting | Token Validation           │ │
│  │  Permission Scoping | Input Validation      │ │
│  └─────────────────────────────────────────────┘ │
└─────────────────────┬───────────────────────────┘
                      │
                      │ Internal API calls
                      │ (never direct DB access)
                      ▼
┌─────────────────────────────────────────────────┐
│              Your SaaS Backend                   │
│                                                  │
│  REST API / GraphQL / gRPC                       │
│  Business Logic | Validation | Authorization     │
│                                                  │
└─────────────────────┬───────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────┐
│                  Database                        │
│  (never directly accessed by MCP server)         │
└─────────────────────────────────────────────────┘

What This Enables for Your SaaS

Once your MCP server is live, your product becomes accessible to every AI agent that supports the protocol.

As of March 2026, the MCP ecosystem includes over 200 server implementations, and most major SaaS platforms have MCP servers, including GitHub, Slack, Google Drive, PostgreSQL, Notion, Jira, and Salesforce.

The products that do not have MCP servers are invisible to AI workflows that connect everything else. A user building an AI agent that reads their Notion pages, creates Jira tickets, and sends Slack messages cannot include your product in that workflow if you have not built the bridge.

If your SaaS product lacks an MCP server, it is invisible to AI agents.

The flip side: once your MCP server is live, your product becomes a natural part of AI-driven workflows. Users who already use Claude or Cursor for their work discover your product through the MCP ecosystem. AI agents recommend your tools when they are the right fit for a task. Your product gets usage it never would have gotten through traditional marketing channels.

The window to build this early and establish your presence in the MCP ecosystem is right now, before the category is crowded. The founders who ship their MCP servers in 2026 will have a year of real usage data, real user feedback on their tool definitions, and established discoverability in the AI agent ecosystem before the founders who wait until it is obviously necessary.

Build it now. The architecture is straightforward, the SDK is well-documented, and the payoff compounds over time.

Quick Reference

Your MCP endpoint format:

https://api.yoursaas.com/mcp

Minimum required to go live:

  • MCP server with at least one tool defined

  • Streamable HTTP transport configured

  • Public HTTPS URL

  • Rate limiting enabled

  • Auth validation for any private tools

Test checklist before launch:

  • Health endpoint returns 200: GET /mcp/health

  • Public tool callable without auth: POST /mcp without Authorization header

  • Private tool rejects requests without auth

  • Private tool validates ownership before returning data

  • Rate limiting blocks excessive requests

  • Endpoint reachable from external network (not just localhost)

  • Error responses do not expose internal details

Where to list your MCP server once live:

  • Your own documentation

  • The MCP server registry at modelcontextprotocol.io

  • Your product's README if open source

  • Founder communities and launch platforms like Founders Today where the audience is building with AI tools

Summarise this article in layman terms
If you don't have time, use this summarise option for fast clarity