MCP-serverontwikkeling
Gebaseerd op rohitg00/awesome-claude-code-toolkit @ ebdf1d5, licentie Apache-2.0
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.
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.
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.
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
{
"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
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: truemet 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.