Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,159 @@
|
||||
// Generated, do not edit. sha256:6e59cd9bc5c34f22b316c129925de56d6e800bca08debd1308bf90cb9f449baa
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { CONFIG } from '../../shared/config.js';
|
||||
import { AI_REFUSALS } from '../ai/gate.js';
|
||||
import { limitText } from '../ai/limits.js';
|
||||
import { ENUMS } from '../../shared/config.js';
|
||||
|
||||
const PARSE_ERROR = -32700;
|
||||
const INVALID_REQUEST = -32600;
|
||||
const METHOD_NOT_FOUND = -32601;
|
||||
const INVALID_PARAMS = -32602;
|
||||
|
||||
const fout = (id, code, message) => ({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
|
||||
const goed = (id, result) => ({ jsonrpc: '2.0', id, result });
|
||||
|
||||
export function createMcpServer({ tools, logCall, onLogError } = {}) {
|
||||
if (typeof logCall !== 'function') {
|
||||
throw new Error('An MCP server without an audit log is not allowed in this project.');
|
||||
}
|
||||
if (!Array.isArray(tools) || tools.length === 0) {
|
||||
throw new Error('An MCP server without tools is meaningless.');
|
||||
}
|
||||
const zonderScope = tools.filter(t => !ENUMS.AI_SCOPE.includes(t?.scope));
|
||||
if (zonderScope.length) {
|
||||
const namen = zonderScope.map(t => t?.name ?? '(naamloos)').join(', ');
|
||||
throw new Error(`Tool without a valid scope: ${namen}. Allowed is ${ENUMS.AI_SCOPE.join(', ')}.`);
|
||||
}
|
||||
const perNaam = new Map(tools.map(t => [t.name, t]));
|
||||
|
||||
const sessie = { id: randomUUID(), client: null, initialized: false };
|
||||
|
||||
async function log(regel, messages) {
|
||||
try {
|
||||
await logCall({
|
||||
sessionId: sessie.id, client: sessie.client,
|
||||
serverVersion: CONFIG.AI.SERVER_VERSION, ...regel,
|
||||
...(messages ? { messages } : {}),
|
||||
});
|
||||
} catch (e) {
|
||||
if (onLogError) onLogError(e);
|
||||
else throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const gesprek = (binnenAt, args, uit) => [
|
||||
{ kind: 'request', text: JSON.stringify(args ?? {}), occurredAt: binnenAt },
|
||||
{ kind: 'response', text: String(uit ?? ''), occurredAt: new Date().toISOString() },
|
||||
];
|
||||
|
||||
async function voerUit(naam, args, droog) {
|
||||
const binnenAt = new Date().toISOString();
|
||||
const tool = perNaam.get(naam);
|
||||
if (!tool) {
|
||||
const regel = { tool: naam, arguments: args, outcome: 'refused', detail: 'Deze tool bestaat niet.' };
|
||||
if (!droog) await log(regel, gesprek(binnenAt, args, regel.detail));
|
||||
return { regel, text: regel.detail, onbekend: true };
|
||||
}
|
||||
try {
|
||||
const { text, fieldsShared } = await tool.run(args, { session: sessie });
|
||||
const { text: uitvoer, capped } = limitText(String(text ?? ''));
|
||||
const regel = {
|
||||
tool: naam, arguments: args, fieldsShared: fieldsShared ?? [],
|
||||
charactersOut: uitvoer.length, outcome: 'ok',
|
||||
detail: capped ? 'Antwoord liep tegen de tekengrens en is ingekort.' : undefined,
|
||||
};
|
||||
if (!droog) await log(regel, gesprek(binnenAt, args, uitvoer));
|
||||
return { regel, text: uitvoer };
|
||||
} catch (e) {
|
||||
const melding = e && e.message ? e.message : String(e);
|
||||
const soort = e && AI_REFUSALS.includes(e.code) ? 'refused' : 'error';
|
||||
const regel = { tool: naam, arguments: args, outcome: soort, detail: melding };
|
||||
if (!droog) await log(regel, gesprek(binnenAt, args, melding));
|
||||
return { regel, text: melding, mislukt: true };
|
||||
}
|
||||
}
|
||||
|
||||
async function toolsCall(id, params) {
|
||||
const naam = params && typeof params.name === 'string' ? params.name : null;
|
||||
const args = params && params.arguments && typeof params.arguments === 'object' ? params.arguments : {};
|
||||
if (!naam) {
|
||||
await log({ tool: '(geen)', outcome: 'refused', detail: 'Verzoek zonder toolnaam.' });
|
||||
return fout(id, INVALID_PARAMS, 'Een tools/call-verzoek noemt geen tool.');
|
||||
}
|
||||
const r = await voerUit(naam, args, false);
|
||||
if (r.onbekend) return fout(id, INVALID_PARAMS, `Onbekende tool '${naam}'.`);
|
||||
if (r.mislukt) return goed(id, { content: [{ type: 'text', text: r.text }], isError: true });
|
||||
return goed(id, { content: [{ type: 'text', text: r.text }] });
|
||||
}
|
||||
|
||||
async function dryRun(naam, args = {}) {
|
||||
const veiligeArgs = args && typeof args === 'object' && !Array.isArray(args) ? args : {};
|
||||
const r = await voerUit(String(naam ?? ''), veiligeArgs, true);
|
||||
return {
|
||||
...r.regel,
|
||||
fieldsShared: r.regel.fieldsShared ?? [],
|
||||
charactersOut: r.regel.charactersOut ?? null,
|
||||
detail: r.regel.detail ?? null,
|
||||
text: r.text,
|
||||
};
|
||||
}
|
||||
|
||||
async function handle(bericht) {
|
||||
if (!bericht || typeof bericht !== 'object' || Array.isArray(bericht)) {
|
||||
return fout(null, INVALID_REQUEST, 'Geen JSON-RPC-object.');
|
||||
}
|
||||
const { id, method, params } = bericht;
|
||||
const isNotificatie = id === undefined || id === null;
|
||||
if (typeof method !== 'string') {
|
||||
return isNotificatie ? null : fout(id, INVALID_REQUEST, 'Verzoek zonder methode.');
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case 'initialize': {
|
||||
sessie.client = params?.clientInfo?.name ? String(params.clientInfo.name) : null;
|
||||
sessie.initialized = true;
|
||||
return goed(id, {
|
||||
protocolVersion: typeof params?.protocolVersion === 'string'
|
||||
? params.protocolVersion : CONFIG.AI.PROTOCOL_VERSION,
|
||||
capabilities: { tools: {} },
|
||||
serverInfo: { name: CONFIG.AI.SERVER_NAME, version: CONFIG.AI.SERVER_VERSION },
|
||||
});
|
||||
}
|
||||
case 'notifications/initialized':
|
||||
case 'initialized':
|
||||
return null;
|
||||
|
||||
case 'ping':
|
||||
return isNotificatie ? null : goed(id, {});
|
||||
|
||||
case 'tools/list': {
|
||||
if (isNotificatie) return null;
|
||||
return goed(id, {
|
||||
tools: tools.map(t => ({
|
||||
name: t.name,
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
inputSchema: t.inputSchema,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
case 'tools/call':
|
||||
if (isNotificatie) return null;
|
||||
return toolsCall(id, params);
|
||||
|
||||
default:
|
||||
return isNotificatie ? null : fout(id, METHOD_NOT_FOUND, `Onbekende methode '${method}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRegel(regel) {
|
||||
let bericht;
|
||||
try { bericht = JSON.parse(regel); } catch { return fout(null, PARSE_ERROR, 'Ongeldige JSON.'); }
|
||||
return handle(bericht);
|
||||
}
|
||||
|
||||
return { handle, handleRegel, dryRun, get session() { return { ...sessie }; } };
|
||||
}
|
||||
Reference in new issue
Block a user