Model Context Protocol (MCP) in 2025: De volledige gids voor SaaS-builders

Als je de afgelopen maanden aandacht hebt besteed aan de AI-tooling-ruimte, is het je waarschijnlijk niet ontgaan dat Model Context Protocol (MCP) is geëvolueerd van "interessante spec van Anthropic" naar "iets dat elk serieus SaaS-product moet ondersteunen." En terecht. MCP geeft AI-agents -- Claude, op GPT gebaseerde assistenten, aangepaste agents -- een gestandaardiseerde manier om uw API te ontdekken en aan te roepen. Zie het als OpenAPI voor het agentic-tijdperk, maar met bidirectionele communicatie in real-time ingebouwd.

Ik heb de afgelopen maanden MCP-servers gebouwd voor verschillende SaaS-producten, en ik wil delen wat echt werkt, wat de documentatie niet vertelt, en welke architectuurbeslissingen ertoe doen. Dit is geen herhaling van de spec. Dit is de gids die ik had willen hebben toen ik begon.

Inhoudsopgave

Wat is het Model Context Protocol (MCP)?

MCP is een open protocol dat oorspronkelijk door Anthropic is gemaakt en bepaalt hoe AI-modellen communiceren met externe tools en gegevensbronnen. Uitgebracht in late 2024 en bereikt v1.0-stabiliteit in begin 2025, wordt het nu ondersteund door Claude Desktop, Cursor, Windsurf, de OpenAI Agents SDK en tientallen andere AI-clients.

Het kernidee: in plaats van dat elke AI-integratie een aangepaste plugin is met een eigen formaat, biedt MCP een enkel protocol dat elke compatibele client kan gebruiken om te ontdekken wat uw service aanbiedt en er mee om te gaan.

Dit is wat MCP bepaalt:

  • Tools -- Functies die de AI kan aanroepen (zoals create_ticket, search_users, generate_report)
  • Resources -- Gegevens die de AI kan lezen (zoals documentatie, databaserecords, configuratiebestanden)
  • Prompts -- Herbruikbare promptsjablonen die uw server kan beschikbaar stellen
  • Sampling -- De mogelijkheid voor uw server om LLM-completions van de client aan te vragen

De transportlaag gebruikt JSON-RPC 2.0 via stdio (voor lokale servers) of HTTP met Server-Sent Events (SSE) voor externe servers. De nieuwere Streamable HTTP-transport, geïntroduceerd in de 2025-03 spec-revisie, is wat u wilt voor elke productie-SaaS-implementatie.

Waarom uw SaaS-product een MCP-server nodig heeft

Laat me direct zijn: als uw SaaS-product een API heeft, zou u nu een MCP-server moeten bouwen. Dit is waarom.

AI-agents worden de primaire API-consumenten. In Q1 2025 meldde Anthropic dat Claude Desktop-gebruikers MCP-tools meer dan 2 miljoen keer per dag aanroepen. Dat getal groeit snel. Uw klanten proberen al AI-agents in te zetten om met uw product om te gaan -- de vraag is of u dat makkelijk of frustrerend maakt.

Het is een distributiekanaal. Wanneer iemand Claude Desktop installeert en zegt "help me mijn projecten beheren", kan de AI MCP-servers ontdekken en gebruiken die de gebruiker heeft geconfigureerd. Uw product wordt toegankelijk via natuurlijke taal. Dat is geen gimmick -- het is een echt nieuw oppervlak voor uw product.

Uw concurrenten doen het. Stripe, Linear, Notion, GitHub, Sentry en Supabase hebben allemaal MCP-servers in de eerste helft van 2025 uitgebracht. Als u in B2B SaaS bent en u hebt er geen, loopt u achter.

Factor Alleen REST API REST API + MCP-server
Toegankelijkheid voor AI-agents Vereist aangepaste integratie per agent Elke MCP-client werkt automatisch
Ontdekking Ontwikkelaars lezen documentatie AI ontdekt mogelijkheden bij runtime
Tijd tot eerste integratie Uren tot dagen Minuten
Interactie in natuurlijke taal Niet mogelijk zonder wrapper Ingebouwd
Onderhoudsbelasting Eén codebase Twee codebases (maar MCP verpakt REST)

MCP-architectuur: hoe het daadwerkelijk werkt

Voordat we code schrijven, moeten we de architectuur duidelijk krijgen. Een MCP-implementatie heeft drie onderdelen:

  1. MCP-client -- De AI-applicatie (Claude Desktop, Cursor, uw aangepaste agent). Het ontdekt de mogelijkheden van uw server en roept ze aan.
  2. MCP-server -- Uw code. Het stelt tools, resources en prompts beschikbaar via het MCP-protocol. Dit is wat we bouwen.
  3. Uw SaaS API -- De werkelijke backend die uw MCP-server aanroept om dingen gedaan te krijgen.

De flow ziet er zo uit:

Gebruiker → AI-client → MCP-client → MCP-server → Uw SaaS API
                                            ↓
                                    Reactie stroomt terug

Uw MCP-server is in wezen een protocoladapter. Het vertaalt tussen het MCP-protocol (dat AI-clients spreken) en uw bestaande REST/GraphQL API. Dit betekent dat u uw bestaande API helemaal niet hoeft te wijzigen. De MCP-server zit ernaast.

Transportopties

Voor SaaS-producten hebt u twee realistische transportopties:

  • Streamable HTTP (aanbevolen): Gebruikt standaard HTTP-aanvragen met optioneel SSE voor streaming. Werkt achter load balancers, CDN's en standaardinfrastructuur. Dit is wat u wilt voor externe/gehoste MCP-servers.
  • SSE (verouderd): Het originele externe transport. Werkt nog steeds, maar de spec beveelt Streamable HTTP aan voor nieuwe implementaties.

Stdio-transport is geweldig voor lokale tools, maar is niet van toepassing op de MCP-server van een SaaS-product.

Uw MCP-serverproject opzetten

Laten we dit bouwen met TypeScript. Het officiële pakket @modelcontextprotocol/sdk is goed onderhouden en is de juiste keuze voor productiegebruik. Python heeft mcp (de officiële Python SDK) als dat uw stack is, maar ik zal me concentreren op TypeScript aangezien de meeste SaaS-backends die ik mee werk Node.js gebruiken.

mkdir my-saas-mcp-server
cd my-saas-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod express
npm install -D typescript @types/node @types/express tsx

Stel uw tsconfig.json in:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Laten we nu de basisserverstructuur maken:

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

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

const server = new McpServer({
  name: "my-saas-mcp",
  version: "1.0.0",
  description: "MCP server for My SaaS Product",
});

// We'll add tools, resources, and prompts here

app.post("/mcp", async (req, res) => {
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => crypto.randomUUID(),
  });
  
  await server.connect(transport);
  await transport.handleRequest(req, res);
});

app.get("/mcp", async (req, res) => {
  // SSE endpoint for streaming responses
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => crypto.randomUUID(),
  });
  
  await server.connect(transport);
  await transport.handleRequest(req, res);
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
  console.log(`MCP server running on port ${PORT}`);
});

Dat is uw skelet. Laten we het invullen.

Tools, resources en prompts definiëren

Tools

Tools zijn de belangrijkste primitive. Elke tool is een functie die de AI kan aanroepen met gestructureerde parameters. Zo definieert u een:

import { z } from "zod";

server.tool(
  "create_project",
  "Create a new project in the workspace",
  {
    name: z.string().describe("The project name"),
    description: z.string().optional().describe("Project description"),
    team_id: z.string().describe("ID of the team that owns this project"),
  },
  async ({ name, description, team_id }) => {
    // Call your SaaS API here
    const project = await apiClient.createProject({ name, description, team_id });
    
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(project, null, 2),
        },
      ],
    };
  }
);

Een paar dingen die ik heb geleerd over tool-ontwerp:

Wees specifiek met beschrijvingen. De AI gebruikt uw tool-beschrijving en parameterbeschrijvingen om te bepalen wanneer en hoe het aan te roepen. Vage beschrijvingen leiden tot verkeerde tool-selectie. "Maak een nieuw project" is oké. "Maak een nieuw project in de werkruimte van de gebruiker. Vereist een team_id die kan worden verkregen uit het list_teams-gereedschap" is veel beter.

Houd het aantal tools beheersbaar. Ik heb mensen gezien die 50+ tools beschikbaar stelden en zich afvroegen waarom de AI in de war raakte. Begin met 10-15 kernbewerkingen. U kunt altijd meer toevoegen.

Retourneer gestructureerde gegevens. Retourneer altijd JSON in uw tekstinhoud. De AI parseert dit beter dan proza.

Resources

Resources stellen leesbare gegevens beschikbaar. Zie ze als GET-eindpunten voor AI-verbruik:

server.resource(
  "project-list",
  "projects://list",
  "List of all projects in the workspace",
  async () => {
    const projects = await apiClient.listProjects();
    return {
      contents: [
        {
          uri: "projects://list",
          mimeType: "application/json",
          text: JSON.stringify(projects, null, 2),
        },
      ],
    };
  }
);

Prompts

Prompts zijn herbruikbare sjablonen. Ze worden weinig gebruikt, maar zijn krachtig:

server.prompt(
  "weekly-report",
  "Generate a weekly status report for a project",
  {
    project_id: z.string().describe("The project ID to report on"),
  },
  async ({ project_id }) => {
    const stats = await apiClient.getProjectStats(project_id);
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `Generate a weekly status report based on this data: ${JSON.stringify(stats)}. Include completed tasks, blockers, and upcoming deadlines.`,
          },
        },
      ],
    };
  }
);

Verbinding maken met uw SaaS API

Uw MCP-server moet met uw bestaande API communiceren. Ik raad aan een getypte API-clientklasse te maken:

// src/api-client.ts
class SaaSApiClient {
  private baseUrl: string;
  private apiKey: string;

  constructor(baseUrl: string, apiKey: string) {
    this.baseUrl = baseUrl;
    this.apiKey = apiKey;
  }

  private async request<T>(path: string, options?: RequestInit): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`, {
      ...options,
      headers: {
        "Authorization": `Bearer ${this.apiKey}`,
        "Content-Type": "application/json",
        ...options?.headers,
      },
    });

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

    return response.json() as T;
  }

  async createProject(data: CreateProjectInput): Promise<Project> {
    return this.request<Project>("/api/v1/projects", {
      method: "POST",
      body: JSON.stringify(data),
    });
  }

  async listProjects(): Promise<Project[]> {
    return this.request<Project[]>("/api/v1/projects");
  }

  // ... more methods
}

Houd deze client dun. Het is een doorvoer, geen bedrijfslogica-laag.

Authenticatie en multi-tenancy

Dit is waar het interessant wordt -- en waar de meeste tutorials de moeilijke onderdelen omzeilen.

Uw MCP-server moet aanvragen op twee manieren authenticeren:

  1. De MCP-client die zich verifieert bij uw MCP-server ("is dit een geldige gebruiker?")
  2. Uw MCP-server die zich verifieert bij uw SaaS API ("handelen namens deze gebruiker")

OAuth 2.0 (aanbevolen voor productie)

De MCP-spec bevat een autorisatieframework op basis van OAuth 2.1. Voor een SaaS-product is dit de juiste aanpak:

// Middleware to extract and validate the OAuth token
app.use("/mcp", async (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing authorization" });
  }

  const token = authHeader.slice(7);
  
  try {
    const user = await validateOAuthToken(token);
    req.user = user;
    next();
  } catch {
    return res.status(401).json({ error: "Invalid token" });
  }
});

Geef vervolgens de gebruikerscontext in uw toolhandlers door. Dit is essentieel voor multi-tenancy -- elke API-oproep moet aan de werkruimte van de geverifieerde gebruiker zijn gebonden.

API-sleutelaanpak (eenvoudiger, voor intern/vroeg stadium)

Als u in een vroeg stadium bent of dit is intern, werken API-sleutels prima:

const apiKey = req.headers["x-api-key"] as string;
const client = new SaaSApiClient(process.env.API_BASE_URL!, apiKey);

De gebruiker geeft zijn API-sleutel op wanneer hij de MCP-server in zijn client configureert, en deze wordt aan uw API doorgegeven.

Foutafhandeling en validatie

AI-agents hebben duidelijke foutberichten nodig. Wanneer een tool mislukt, moet de AI begrijpen waarom, zodat het de invoer kan corrigeren of het probleem aan de gebruiker kan uitleggen.

server.tool(
  "get_project",
  "Get project details by ID",
  { project_id: z.string().uuid().describe("The project's UUID") },
  async ({ project_id }) => {
    try {
      const project = await apiClient.getProject(project_id);
      return {
        content: [{ type: "text", text: JSON.stringify(project, null, 2) }],
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : "Unknown error";
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: `Failed to get project: ${message}. Make sure the project_id is a valid UUID and the project exists in your workspace.`,
          },
        ],
      };
    }
  }
);

Merk op de isError: true-vlag. Dit vertelt de AI-client dat de tool-oproep is mislukt, zodat het dit op de juiste manier kan afhandelen. Voeg altijd bruikbare begeleiding in foutberichten op.

Uw MCP-server testen

Het testen van MCP-servers is in 2025 veel gemakkelijker geworden. Dit zijn uw opties:

MCP Inspector

De officiële MCP Inspector is uw beste vriend tijdens development:

npx @modelcontextprotocol/inspector

Dit geeft u een webinterface waar u verbinding kunt maken met uw server, tools/resources kunt bladeren en ze interactief kunt aanroepen. Gebruik het voortdurend.

Geautomatiseerd testen

Voor CI/CD test u uw tools als normale async functies:

import { describe, it, expect } from "vitest";

describe("create_project tool", () => {
  it("should create a project with valid input", async () => {
    const result = await createProjectHandler({
      name: "Test Project",
      team_id: "team-123",
    });

    expect(result.isError).toBeUndefined();
    const data = JSON.parse(result.content[0].text);
    expect(data.name).toBe("Test Project");
  });

  it("should return error for missing team_id", async () => {
    // Zod validation should catch this before the handler runs
    // Test the validation layer
  });
});

Integratietesting met Claude Desktop

Zodra uw server draait, voegt u het toe aan de config van Claude Desktop:

{
  "mcpServers": {
    "my-saas": {
      "url": "http://localhost:3001/mcp",
      "headers": {
        "Authorization": "Bearer your-test-token"
      }
    }
  }
}

Spreek dan gewoon met Claude en probeer uw tools natuurlijk te gebruiken. U zult snel randgevallen vinden die de geautomatiseerde tests missen.

Implementatie en productieoverwegingen

Waar moet u implementeren

Uw MCP-server is gewoon een Express-app. Implementeer deze overal waar u Node.js-services implementeert. Enkele goede opties:

Platform Koude start Kosten (geraamd) Geschikt voor
Railway Geen ~€5-20/mnd Kleine tot middel SaaS
Fly.io <500ms ~€5-15/mnd Wereldwijde distributie
AWS ECS/Fargate Geen ~€15-50/mnd Enterprise, bestaande AWS
Vercel (Edge) <100ms €0-20/mnd Als u al op Vercel bent
Cloudflare Workers <5ms €0-5/mnd Prestatiekritiek

Tarieflimitering

AI-agents kunnen veel vragen stellen. Een enkel gebruikersconversatie kan 20-30 tooloproepen activeren. Implementeer tarieflimitering die genereus genoeg is voor normaal AI-gebruik, maar misbruik voorkomt:

import rateLimit from "express-rate-limit";

const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 60, // 60 requests per minute per user
  keyGenerator: (req) => req.user?.id || req.ip,
});

app.use("/mcp", limiter);

Monitoring

Log elke tool-aanroeping met gebruikers-ID, tool-naam en latentie. U wilt zichtbaarheid in welke tools het meest worden gebruikt, welke het meest mislukken, en waar de latentieknelpunten zich bevinden. Datadog, Axiom, of zelfs gestructureerde JSON-logs naar CloudWatch werken prima.

Versiebeheer

Uw MCP-server zal zich ontwikkelen. Gebruik het version-veld in uw servermetagegevens en overweeg om meerdere versies achter een padprefix (/mcp/v1, /mcp/v2) uit te voeren tijdens overgangen.

Echt voorbeeld: een MCP-server bouwen voor een projectmanagement SaaS

Laat me een echt voorbeeld doorlopen. Stel, u bouwt een MCP-server voor een projectmanagement tool (denk aan een vereenvoudigd Linear of Asana).

Dit is de tool-set die ik zou beschikbaar stellen:

// Core CRUD tools
server.tool("list_projects", ...);
server.tool("get_project", ...);
server.tool("create_project", ...);
server.tool("update_project", ...);

// Task management
server.tool("list_tasks", ...);  // with filters for status, assignee, project
server.tool("create_task", ...);
server.tool("update_task", ...); // update status, assignee, priority
server.tool("add_comment", ...);

// Search and reporting
server.tool("search", ...);      // full-text search across projects and tasks
server.tool("get_project_stats", ...); // summary stats for a project

// Resources
server.resource("workspace-info", ...); // workspace config, team members

// Prompts
server.prompt("standup-report", ...);   // generate a standup from recent activity
server.prompt("sprint-planning", ...);  // help plan a sprint

Dat zijn 12 tools, 1 resource en 2 prompts. Genoeg om echt bruikbaar te zijn zonder de tool-selectie van de AI te overweldigen.

De gebruikerservaring ziet er als volgt uit: iemand opent Claude Desktop en zegt "Welke taken zijn achterstallig in het Backend Rewrite-project?" Claude roept list_tasks aan met een statusfilter en projectnaam, krijgt de resultaten en presenteert ze in natuurlijke taal. De gebruiker zegt "Wijs de authentication migration-taak toe aan Sarah en verhoog het naar hoge prioriteit." Claude roept update_task aan. Het voelt magisch aan, en het is eigenlijk gewoon protocolinstallatie.

Als u iets dergelijks bouwt en hulp wilt bij de Next.js frontend of de headless CMS-laag die dergelijke projecten vaak begeleidt, dat is iets wat we veel doen bij Social Animal. Maar de MCP-server zelf? Dat kunt u zeker in-house bouwen met deze gids.

Veelgestelde vragen

Wat is het verschil tussen MCP en function calling? Function calling (zoals OpenAI's function calling of Claude's tool use) is hoe een AI-model bepaalt dat het een functie moet aanroepen binnen een enkele API-oproep. MCP is het protocol dat een AI-client in staat stelt om te ontdekken welke functies beschikbaar zijn van externe servers. Ze werken samen -- de AI-client gebruikt function calling intern om te bepalen wanneer het een MCP-tool moet aanroepen. Beschouw MCP als de leidingen tussen systemen en function calling als het besluitvormingsproces van het model.

Hoeveel kost het om een MCP-server te bouwen en uit te voeren? De server zelf is licht. Voor een typisch SaaS-product met 10-20 tools bent u naar enkele honderden regels TypeScript aan het kijken. Hostingkosten €5-50/maand afhankelijk van uw verkeer en platform. De werkelijke kosten zijn ontwikkelaarstijd -- begroot 2-4 weken voor een productie-kwaliteit MCP-server met auth, foutafhandeling, monitoring en tests. Als dat veel lijkt, hebben we teams geholpen deze sneller uit te brengen. Controleer onze prijzenpagina voor details.

Kan ik in plaats daarvan Python gebruiken? Absoluut. De officiële Python SDK (pip install mcp) is uitstekend en heeft naar mijn mening betere ergonomie voor tool-definities. Gebruik wat uw team kent. Het protocol is taal-agnostisch. Als uw SaaS-backend Python is (Django, FastAPI), is het bouwen van de MCP-server in Python nog logischer omdat u modellen en validatielogica kunt delen.

Moet ik mijn bestaande API wijzigen? Nee. Uw MCP-server is een afzonderlijke service die uw bestaande API aanroept. Het is een adapterlaag. Dat gezegd hebbende, zou u merken dat u enkele API-eindpunten specifiek voor AI-verbruik wilt toevoegen -- zoals een search-eindpunt dat meer context retourneert dan uw UI nodig heeft. Dat is prima. Maar het is additief, geen wijziging.

Hoe handel ik langlopende bewerkingen af? Sommige tools kunnen bewerkingen activeren die minuten duren (zoals het genereren van een rapport of het verwerken van een grote import). Gebruik de feature voor voortgangsberichten van MCP om de client op de hoogte te houden. Uw tool-handler kan voortgangsupdates verzenden terwijl het op de bewerking wacht. Voor zeer lange bewerkingen (>30 seconden), kunt u overwegen onmiddellijk terug te keren met een job-ID en een afzonderlijk check_job_status-tool te bieden.

Is MCP stabiel genoeg voor productie? Ja, vanaf midden 2025. De spec bereikte v1.0 in maart 2025, en de 2025-03-26 revisie (die Streamable HTTP toevoegde) is wat grote clients hebben aangenomen. Anthropic, Microsoft, Google en OpenAI investeren allemaal in het protocol. Het gaat niet weg. Dat gezegd hebbende, houdt u spec-updates in de gaten -- er zijn actieve voorstellen rond betere auth-flows en server-naar-server communicatie.

Wat is de beste manier om paginering in MCP-tools af te handelen? Retourneer standaard een redelijk aantal resultaten (20-50 items) en accepteer cursor- of page-parameters. Voeg paginametagegensgegevens in uw reactie op zodat de AI weet dat er meer resultaten zijn. Zoiets als: { results: [...], next_cursor: "abc123", total_count: 342 }. De AI zal natuurlijk de volgende pagina aanvragen als de gebruiker meer gegevens nodig heeft.

Kan één MCP-server tegelijkertijd meerdere AI-clients ondersteunen? Ja, en dat zou het moeten doen. Uw MCP-server is gewoon een HTTP-server die gelijktijdige aanvragen afhandelt. Elke aanvraag bevat het verificatietoken van de gebruiker, dus u bereikt alles op de juiste tenant. Er is geen client-specifieke staat waarmee u zich zorgen hoeft te maken als u de Streamable HTTP-transport correct gebruikt. Behandel het als elke andere stateless API-server.