AI-skillGratis

MCP-serverontwikkeling

Gebaseerd op rohitg00/awesome-claude-code-toolkit @ ebdf1d5, licentie Apache-2.0

Bijgewerkt 30 augustus 2026

Dit bestand is door ToolBrain vertaald en inhoudelijk gewijzigd op 2026-08-30, op basis van mcp-development uit rohitg00/awesome-claude-code-toolkit (Apache-2.0), zie https://github.com/rohitg00/awesome-claude-code-toolkit/blob/ebdf1d596d2cde5c5cceb32177e8d1cf4829e7d9/skills/mcp-development/SKILL.md en ../LICENSES/awesome-claude-code-toolkit-APACHE-2.0.txt.

Een MCP-server (Model Context Protocol) is de brug tussen een LLM en je eigen systemen: tools die de LLM kan aanroepen, resources die het model kan raadplegen, en promptsjablonen als herbruikbare startpunten. Onderstaande patronen gelden voor elke MCP-server die je zelf bouwt, bijvoorbeeld om Claude of een n8n-workflow toegang te geven tot een interne database, een bestandensysteem of een bedrijfsapplicatie.

Een MCP-server met tools opzetten

Een minimale server registreert tools met een naam, een beschrijving en een inputschema. De beschrijving is niet decoratief: het model beslist op basis daarvan wanneer het de tool inzet, dus een vage omschrijving levert een tool op die nooit (of verkeerd) wordt aangeroepen.

typescript
Toon alle 51 regels
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "project-tools",
  version: "1.0.0",
});

server.tool(
  "search_files",
  "Zoek bestanden die overeenkomen met een glob-patroon in de projectmap",
  {
    pattern: z.string().describe("Glob-patroon (bijv. '**/*.ts')"),
    directory: z.string().optional().describe("Basismap om vanaf te zoeken"),
  },
  async ({ pattern, directory }) => {
    const files = await glob(pattern, { cwd: directory ?? process.cwd() });
    return {
      content: [
        {
          type: "text",
          text: files.length > 0
            ? files.join("\n")
            : `Geen bestanden gevonden voor ${pattern}`,
        },
      ],
    };
  }
);

server.tool(
  "run_query",
  "Voer een read-only SQL-query uit tegen de applicatiedatabase",
  {
    query: z.string().describe("SQL SELECT-query om uit te voeren"),
    limit: z.number().default(100).describe("Maximum aantal rijen"),
  },
  async ({ query, limit }) => {
    if (!query.trim().toUpperCase().startsWith("SELECT")) {
      return {
        content: [{ type: "text", text: "Alleen SELECT-queries zijn toegestaan" }],
        isError: true,
      };
    }
    const rows = await db.query(`${query} LIMIT ${limit}`);
    return {
      content: [{ type: "text", text: JSON.stringify(rows, null, 2) }],
    };
  }
);

Merk op dat de run_query-tool een harde whitelist afdwingt (alleen SELECT) vóórdat de query de database bereikt — bij een eigen MCP-server op productiedata is dat een minimale eis, geen extraatje.

Resources: alleen-lezen data

Resources geven het model context zonder dat er een expliciete tool-aanroep nodig is, bijvoorbeeld een databaseschema of een configuratie. Redigeer secrets altijd vóór je ze teruggeeft.

typescript
Toon alle 41 regels
server.resource(
  "schema",
  "db://schema",
  "Huidig databaseschema met alle tabellen, kolommen en relaties",
  async () => {
    const schema = await db.query(`
      SELECT table_name, column_name, data_type, is_nullable
      FROM information_schema.columns
      WHERE table_schema = 'public'
      ORDER BY table_name, ordinal_position
    `);
    return {
      contents: [
        {
          uri: "db://schema",
          mimeType: "application/json",
          text: JSON.stringify(schema, null, 2),
        },
      ],
    };
  }
);

server.resource(
  "config",
  "config://app",
  "Applicatieconfiguratie (secrets geredigeerd)",
  async () => {
    const config = await loadConfig();
    const safe = redactSecrets(config);
    return {
      contents: [
        {
          uri: "config://app",
          mimeType: "application/json",
          text: JSON.stringify(safe, null, 2),
        },
      ],
    };
  }
);

Promptsjablonen

Een prompt-endpoint levert een kant-en-klaar, geparametriseerd startpunt voor een terugkerende taak, zoals een code-review met een instelbare focus.

typescript
server.prompt(
  "review-code",
  "Beoordeel codewijzigingen op bugs, security-issues en stijl",
  {
    diff: z.string().describe("Git-diff of code om te beoordelen"),
    focus: z.enum(["security", "performance", "style", "all"]).default("all"),
  },
  async ({ diff, focus }) => ({
    messages: [
      {
        role: "user",
        content: {
          type: "text",
          text: `Beoordeel deze code-diff. Focus: ${focus}\n\n${diff}`,
        },
      },
    ],
  })
);

Clientconfiguratie

json
{
  "mcpServers": {
    "project-tools": {
      "command": "node",
      "args": ["./mcp-server/dist/index.js"],
      "env": {
        "DATABASE_URL": "postgres://localhost:5432/app"
      }
    },
    "remote-server": {
      "url": "https://mcp.example.com/sse",
      "headers": {
        "Authorization": "Bearer ${MCP_TOKEN}"
      }
    }
  }
}

Transport

typescript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const transport = new StdioServerTransport();
await server.connect(transport);

Voor HTTP-gebaseerde servers gebruik je de SSE-transport voor streamende responses naar clients.

Veelgemaakte fouten

  • Tools met een vage beschrijving die niet duidelijk maakt wanneer je ze inzet.
  • Input niet valideren met een schema (bijv. Zod) vóór verwerking.
  • Ruwe error-stacktraces teruggeven aan de client.
  • De isError: true-vlag vergeten bij foutresponses.
  • Te veel fijnmazige tools bouwen in plaats van een paar samenstelbare.
  • Secrets niet redigeren in resource-responses.

Checklist

  • Elke tool heeft een duidelijke beschrijving van wanneer en waarom je 'm gebruikt
  • Inputparameters gevalideerd met een schema en begrijpelijke foutmeldingen
  • Foutresponses bevatten isError: true met een gebruiksvriendelijke boodschap
  • Resources geven alleen-lezen data met secrets geredigeerd
  • Promptsjablonen bieden gestructureerde startpunten voor terugkerende taken
  • De server sluit netjes af bij SIGINT/SIGTERM
  • Tools zijn samenstelbaar (doen één ding goed) in plaats van monolithisch
  • Clientconfiguratie is gedocumenteerd met de vereiste omgevingsvariabelen

Praktijkvoorbeeld (NL)

Een Nederlandse groothandel in bouwmaterialen laat haar Claude-gebaseerde inkoopassistent draaien via een eigen MCP-server die bovenop de bestaande voorraad-database staat. De server registreert precies twee tools: zoek_artikel (zoekt op artikelnummer of omschrijving, alleen lezend) en check_voorraadniveau (geeft actuele voorraad en levertijd per magazijn terug). Er is bewust geen schrijvende tool toegevoegd — bestellingen plaatsen blijft mensenwerk in het ERP-systeem. De n8n-workflow die 's ochtends een voorraadrapportage samenstelt, roept dezelfde MCP-server aan als de Claude-chat die inkopers gebruiken, zodat er precies één plek is waar de databasequery's gevalideerd en de klantgegevens geredigeerd worden, in plaats van dezelfde SQL-logica dubbel te onderhouden in de workflow én in de chatbot.