Summary
In 2026, more of your customers are asking their AI assistant to do the work instead of opening your dashboard. The Model Context Protocol (MCP) is the open standard that lets assistants such as Claude, ChatGPT, GitHub Copilot and custom LangChain agents discover and call your product's capabilities through one consistent interface. If you run a SaaS product, an MCP server is quickly becoming the "API for agents" that sits next to your REST API.
This guide is written for CTOs, founders and senior engineers. It explains what an MCP server actually is, when building one is worth it, how to design tools that agents use correctly, and how to ship a secure server in Laravel (with the official laravel/mcp package) or Node.js (with the official TypeScript SDK). It also covers authentication, tenant isolation, rate limiting and the mistakes we see most often when teams expose their product to AI agents.
What an MCP server is (in plain terms)
MCP defines how an AI client talks to an external system. Your side of that conversation is the MCP server. It exposes three kinds of capability:
- Tools: actions the model can call, such as
search_invoices,create_ticketorget_shipment_status. Each tool has a name, a description and a JSON Schema for its inputs. - Resources: read-only context the client can load, such as documentation, a customer's plan details or a report.
- Prompts: reusable prompt templates your product recommends for common jobs.
Remote servers are usually served over the Streamable HTTP transport: the client sends JSON-RPC messages with HTTP POST to a single endpoint (for example /mcp), and the server can reply with JSON or open a Server-Sent Events stream for longer work. Local servers, used by developer tools, run as a command over stdio.
The key difference from a normal REST API is who reads the contract. A REST API is read by a developer who writes integration code. An MCP server is read by a language model at runtime, which decides which tool to call based on names, descriptions and schemas. That changes how you design it.
Should your SaaS build an MCP server in 2026?
Major vendors have been shipping official MCP servers through 2025 and 2026, and independent trackers such as Cyclr's State of MCP report show the fastest growth is in servers that run workflows, not just search data. Still, it is not the right move for every product. A practical checklist:
Build one if
- Your users repeatedly ask questions of their data ("Which invoices are overdue for more than 30 days?") or perform repetitive multi-step actions.
- You already have a clean, permission-aware service layer or REST API you can reuse.
- Your buyers are technical teams (developer tools, DevOps, data, CRM, logistics, fintech operations) who already use AI assistants daily.
- Competitors in your category are listing "works with ChatGPT/Claude" as a feature.
Wait if
- Your permission model is inconsistent between UI and API. An agent will find every gap.
- Most valuable actions are irreversible and high-risk (moving money, deleting data) and you have no confirmation or audit flow yet.
- You have not yet got basic API rate limiting and logging in place.
Designing tools that agents use correctly
Most failed MCP projects do not fail on code. They fail because the tool surface is a one-to-one copy of the REST API, with 80 endpoints and vague descriptions. Models then call the wrong tool, chain five calls where one would do, or pass invalid arguments. These rules help:
- Design around outcomes, not endpoints. Prefer
find_overdue_invoices(days, customer)over exposing rawGET /invoiceswith fifteen filters. - Keep the list short. Start with 5–15 high-value tools. Every extra tool makes selection harder for the model and adds tokens to every request.
- Write descriptions for a model. Say what the tool does, when to use it, when not to use it and what units or formats inputs use.
- Use tight schemas. Enums, min/max, required fields and ISO date formats remove a whole class of errors.
- Return compact, structured results. Return the 10 fields that matter, plus IDs and a link back to the UI, not a 200 KB JSON dump that eats the context window.
- Return actionable errors. "customer_id not found; call search_customers first" lets the agent self-correct.
- Mark read-only and destructive tools. MCP supports annotations such as read-only, destructive and idempotent hints, which clients use to decide when to ask the user for confirmation.
Building an MCP server in Laravel
Laravel now ships an official package, laravel/mcp, that turns servers, tools, resources and prompts into ordinary Laravel classes with dependency injection, validation and middleware. Install it and publish the AI routes file:
composer require laravel/mcp
php artisan vendor:publish --tag=ai-routes
php artisan make:mcp-server BillingServer
php artisan make:mcp-tool FindOverdueInvoicesTool
A read-only tool scoped to the current tenant
<?php
namespace App\Mcp\Tools;
use App\Models\Invoice;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
#[IsReadOnly]
#[Description('Lists unpaid invoices that are overdue by at least the given number of days for the current account. Use this before drafting payment reminders. Amounts are in the invoice currency.')]
class FindOverdueInvoicesTool extends Tool
{
public function handle(Request $request): Response
{
$data = $request->validate([
'days' => ['required', 'integer', 'min:1', 'max:365'],
'limit' => ['nullable', 'integer', 'min:1', 'max:50'],
], [
'days.required' => 'Pass "days" as a whole number, for example 30.',
]);
$user = $request->user();
if (! $user->can('viewAny', Invoice::class)) {
return Response::error('You do not have permission to view invoices.');
}
$invoices = Invoice::query()
->where('account_id', $user->account_id) // tenant isolation
->whereNull('paid_at')
->where('due_date', '<=', now()->subDays($data['days']))
->orderBy('due_date')
->limit($data['limit'] ?? 20)
->get(['id', 'number', 'customer_name', 'total', 'currency', 'due_date']);
return Response::structured([
'count' => $invoices->count(),
'invoices' => $invoices->map(fn ($i) => [
'id' => $i->id,
'number' => $i->number,
'customer' => $i->customer_name,
'amount' => $i->total,
'currency' => $i->currency,
'due_date' => $i->due_date->toDateString(),
'url' => route('invoices.show', $i),
]),
]);
}
public function schema(JsonSchema $schema): array
{
return [
'days' => $schema->integer()->description('Minimum days past the due date, e.g. 30.')->required(),
'limit' => $schema->integer()->description('Maximum invoices to return (1-50). Defaults to 20.'),
];
}
}
Register the server with authentication and rate limiting
// app/Mcp/Servers/BillingServer.php
protected array $tools = [
\App\Mcp\Tools\FindOverdueInvoicesTool::class,
];
// routes/ai.php
use App\Mcp\Servers\BillingServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::oauthRoutes(); // OAuth discovery + client registration (uses Passport)
Mcp::web('/mcp/billing', BillingServer::class)
->middleware(['auth:api', 'throttle:mcp']);
The MCP specification documents OAuth 2.1 as its authorisation mechanism, and Laravel's docs recommend Passport for that reason. If your app already uses Sanctum and your target clients accept a bearer token, auth:sanctum is a simpler starting point. We compared the trade-offs in Laravel Sanctum vs Passport vs JWT in 2026.
Test locally with php artisan mcp:inspector mcp/billing, and write normal Pest or PHPUnit tests with BillingServer::actingAs($user)->tool(FindOverdueInvoicesTool::class, ['days' => 30])->assertOk().
Building an MCP server in Node.js (TypeScript)
For Node.js products, the official @modelcontextprotocol/sdk package provides the server, transports and Zod-based schemas. Below is a stateless Streamable HTTP server on Express, which is the easiest shape to run behind a load balancer or on serverless containers.
npm install @modelcontextprotocol/sdk zod express
import express from "express";
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { verifyAccessToken } from "./auth.js"; // your OAuth / token check
import { findShipments } from "./services/shipments.js";
function buildServer(accountId: string) {
const server = new McpServer({ name: "acme-logistics", version: "1.0.0" });
server.registerTool(
"get_delayed_shipments",
{
title: "Get delayed shipments",
description:
"Lists shipments for the current account that are past their promised delivery date. " +
"Use for status questions; does not change any data.",
inputSchema: {
minHoursLate: z.number().int().min(1).max(720).describe("Minimum hours late, e.g. 24"),
limit: z.number().int().min(1).max(50).default(20),
},
annotations: { readOnlyHint: true },
},
async ({ minHoursLate, limit }) => {
const rows = await findShipments({ accountId, minHoursLate, limit });
return {
content: [{ type: "text", text: JSON.stringify({ count: rows.length, shipments: rows }) }],
};
}
);
return server;
}
const app = express();
app.use(express.json({ limit: "1mb" }));
app.post("/mcp", async (req, res) => {
const auth = await verifyAccessToken(req.headers.authorization);
if (!auth) {
res.status(401).set("WWW-Authenticate", 'Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"').end();
return;
}
const server = buildServer(auth.accountId);
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); // stateless
res.on("close", () => { transport.close(); server.close(); });
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(3000);
Notice that the account ID comes from the verified token, never from a tool argument. The model should not be able to choose which tenant it reads. If you are already adding AI features to an existing app, our guide on integrating AI into a Laravel or Node.js app covers the surrounding architecture.
Security checklist for production MCP servers
An MCP server is an API that a probabilistic client calls on behalf of a user, sometimes after reading untrusted content such as emails or web pages. Treat it with at least the same care as your public API, and add a few agent-specific controls:
- Per-user authorisation, not a shared key. Use OAuth 2.1 (or scoped personal tokens) so every call runs with the real user's permissions and can be revoked.
- Tenant from the token. Derive account and user IDs server-side. Never accept them as tool arguments.
- Least privilege by default. Launch read-only, then add write tools one at a time with clear scopes.
- Confirmation for destructive actions. Mark them destructive, require an explicit
confirmargument, or create a pending action the user approves in your UI. - Prompt-injection awareness. Text you return (customer notes, ticket bodies) can contain instructions aimed at the model. Label untrusted content and keep high-risk tools out of the same server as tools that return arbitrary user text where you can.
- Rate limits and quotas per user and per client, since agents can loop.
- Audit logging of every tool call: user, client, tool, arguments (redacted) and result status.
- Validate the Origin header and bind local servers to localhost, as the transport specification recommends, to prevent DNS rebinding attacks.
The same principles appear in the OWASP API Security Top 10; broken object-level authorisation is just as dangerous when the caller is an agent.
Recommended architecture
For most SaaS products we recommend this shape:
- Service layer first. Tools call the same domain services and policies your controllers use, so there is one source of truth for business rules.
- A dedicated MCP endpoint (for example
https://api.yourapp.com/mcp) behind the same gateway, WAF and observability as your API. - Stateless servers wherever possible, so you can scale horizontally on ECS, Cloud Run or Kubernetes without sticky sessions.
- Background jobs for slow work. Return a job ID quickly and offer a
get_job_statustool, or stream progress notifications. - Versioned tool contracts. Changing a tool's name or schema can silently break customers' saved agent workflows, so deprecate rather than rename.
Effort and timeline
Effort depends far more on the state of your existing API and permissions than on MCP itself. As a rough guide from typical projects: a read-only server with a handful of tools on top of a clean service layer can usually be built and tested in one to three weeks. Adding OAuth 2.1, write actions with confirmation flows, audit logging and listing in client directories typically extends that to six to ten weeks. If your permissions need refactoring first, budget for that separately; it is usually the biggest risk.
Common mistakes to avoid
- Auto-generating one tool per REST endpoint and shipping 100+ tools.
- Returning huge payloads that blow the model's context window.
- Using a single admin API key for every customer.
- Skipping evaluation. Keep a small set of real user requests and check the agent picks the right tools after every change.
- Forgetting humans: always include links back to the relevant screen in your product so users can verify.
Conclusion
An MCP server lets AI assistants use your product safely on behalf of your customers. The protocol is the easy part. What makes it work is a short list of well-described, outcome-based tools, per-user authorisation, strict tenant isolation, and logging you can audit. Laravel and Node.js both have official, production-ready tooling, so the main investment is in design and security, not plumbing.
Planning to make your SaaS agent-ready? DayDecoder builds and secures MCP servers, AI agents and AI integrations for Laravel and Node.js products, working with teams in the US, UK, EU and Australia. You can also hire dedicated AI agent developers. Contact us for a free quote and a short review of your API's readiness for AI agents.
FAQ
What is an MCP server?
An MCP (Model Context Protocol) server is the part of your product that exposes tools, resources and prompts to AI assistants such as Claude, ChatGPT or custom agents through a standard protocol, so they can read data and perform actions on a user's behalf.
Is an MCP server a replacement for my REST API?
No. It usually sits alongside your REST API and calls the same service layer. REST serves developers writing integrations; MCP serves AI clients that choose tools at runtime from their names, descriptions and schemas.
Can I build an MCP server in Laravel?
Yes. The official laravel/mcp package lets you define servers, tools, resources and prompts as Laravel classes, protect them with Passport (OAuth 2.1) or Sanctum middleware, and test them with the MCP Inspector or Pest/PHPUnit.
How should an MCP server be authenticated?
The MCP specification documents OAuth 2.1 for remote servers, so each call runs with a real user's permissions and can be revoked. Scoped bearer tokens can work for clients that support them. Avoid one shared admin key for all customers.
How many tools should an MCP server expose?
Start small, typically 5 to 15 outcome-based tools with clear descriptions and tight input schemas. Large lists copied from every REST endpoint make it harder for models to pick the right tool and waste context.
How long does it take to build a production MCP server?
On top of a clean API and permission model, a read-only server with a few tools often takes one to three weeks. Adding OAuth, write actions with confirmation, audit logging and hardening typically takes six to ten weeks. Refactoring inconsistent permissions is extra.
Written by
Prakash Tank
Founder & Full-Stack Architect, DayDecoder
Prakash started as a certified Laravel developer and grew into a full-stack architect who owns a project end to end: requirements, architecture, development, deployment, and support. He designs event-driven systems with...
More about Prakash