
How to Create an MCP Server URL and Give AI Access to Your SaaS
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:
The AI client detects your MCP server requires OAuth
It redirects the user to your authorization URL
The user logs in and grants permission
Your server issues an access token
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/healthPublic tool callable without auth:
POST /mcpwithout Authorization headerPrivate 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


