
Een MCP-server kan netjes stateless communiceren en toch onveilig worden gebruikt. Dat gebeurt wanneer een agentruntime één client, header-map, consentstatus of toolcache deelt tussen gelijktijdige runs. Dan kan een verzoek van tenant A per ongeluk context van tenant B krijgen. Daarom is MCP session isolation een testbare ontwerpkeuze, geen aanname.
Dit is een neutrale demo met synthetische tenants en records, geen productieproef. Microsoft publiceerde op 18 september 2026 Agent Framework Python 1.19.0. Die release scoped provider-backed MCP-sessies per invocation. Dat is een goede veiligheidsverbetering, maar geen vrijbrief om een eigen proxy, cache of downstream-database niet meer te testen.
Waarom maakt een stateless protocol je runtime niet automatisch stateless?
Nee. Een protocol zonder server-side gesprekstoestand bepaalt niet hoe jouw applicatie clientobjecten, cookies, tokens, consent of resultaten in het geheugen bewaart.
Zie een MCP-call als vier lagen die elk eigendom nodig hebben:
| Laag | Hoort bij | Mag niet stilzwijgend delen |
|---|---|---|
| Run | Eén gebruikersvraag of workflowstap | runId, trace en foutstatus |
| Identity | De geautoriseerde aanvrager | token, tenant en scopes |
| MCP-sessie/client | Eén run of aantoonbaar veilige transportpool | cookies, session state, headers |
| Tool en data | De server-side autorisatiegrens | records van een andere tenant |
De release notes van Agent Framework 1.19.0 noemen zowel per-invocation MCP-sessies als scoping naar identity, origin en ownership. Ze bewijzen niet dat een globaal client, een reverse proxy-cache of je tool zelf die eigendom ook goed behandelt.
Wat is een bruikbaar sessiecontract?
Een bruikbaar contract maakt vóór de toolcall zichtbaar wie de run bezit. In deze demo krijgt iedere run een eigen identitycontext, client en trace. De server controleert de tenant opnieuw; een header is geen autorisatiebewijs op zichzelf.
type RunContext = {
runId: string;
tenantId: "north" | "south";
accessToken: string; // in werkelijkheid: kortlevend en niet loggen
traceId: string;
};
async function invokeCaseTool(ctx: RunContext, caseId: string) {
const client = createMcpClient({
headers: {
Authorization: `Bearer ${ctx.accessToken}`,
"X-Tenant-Id": ctx.tenantId,
"X-Run-Id": ctx.runId,
},
});
return client.callTool("read_case", { caseId, tenantId: ctx.tenantId });
}
Dit is pseudocode, geen bewezen SDK-configuratie. Het punt is de levensduur: maak de client en mutable headers binnen de run, of bewijs dat een transportpool geen identity- of sessiestate hergebruikt. Zet een token nooit in een trace, foutmelding of snapshot.
Dezelfde scheiding helpt ook bij een MCP-server zonder sessies. Daar gaat het om protocolmigratie; hier gaat het om wat de applicatie tussen twee calls bewaart.
Hoe test je MCP session isolation met twee parallelle runs?
Maak eerst twee volledig synthetische tenants. Beide hebben een record met hetzelfde herkenbare type, maar een andere geheime markering. Zo zie je direct wanneer een cache of header in de verkeerde run terechtkomt.
const north = makeRun({ tenantId: "north", token: "test-north" });
const south = makeRun({ tenantId: "south", token: "test-south" });
const [northResult, southResult] = await Promise.all([
invokeCaseTool(north, "case-17"),
invokeCaseTool(south, "case-17"),
]);
assert.equal(northResult.tenantId, "north");
assert.equal(southResult.tenantId, "south");
assert.notEqual(north.traceId, south.traceId);
assert.notEqual(northResult.marker, southResult.marker);
Voer niet alleen de gelukkige route uit. Een goede negatieve test probeert bewust state te laten lekken:
| Test | Verwachting |
|---|---|
Parallelle calls met hetzelfde caseId |
Ieder resultaat hoort bij de eigen tenant |
| Header uit run A in client van run B | Server weigert of negeert de mismatch |
| Consent in A, pending consent in B | B kan niet hervatten op basis van A |
| Fout of reconnect in A | B houdt eigen trace, token en toolresultaat |
| Cache-hit met identiek record-ID | Cache-key bevat tenant én relevante identitycontext |
Een test is geslaagd als je een concrete assertie hebt, niet als de terminal geen fout toont. Gebruik bij parallelle tests een barrière of vertraagde dummytool zodat calls echt overlappen; anders test je per ongeluk alleen twee seriële verzoeken.
Welke observability heb je nodig zonder privacy te verliezen?
Leg minimaal vast dat de grens is bewaakt: runId, gehashte tenantreferentie, toolnaam, uitkomst, approvalstatus en trace-ID. Bewaar niet standaard volledige prompts, Authorization-headers, consentgegevens of toolinhoud.
{
"event": "mcp_tool_completed",
"run_id": "r-82",
"tenant_ref": "sha256:…",
"tool": "read_case",
"trace_id": "t-91",
"result": "allowed"
}
Vergelijk na een parallelle test de tracebomen. Een toolcall uit run A mag nooit onder de trace van B verschijnen. Is dat wel zo, stop dan de release en onderzoek eerst clientlifecycle, context propagation en cachekeys. Meer logs zijn niet automatisch beter: bij AI-agenten in productie hoort observability samen te gaan met dataminimalisatie.
Welke foutmodus zie je het vaakst?
De klassieker is een globale client met mutable default headers. Run A overschrijft de header, de event loop schakelt naar run B en die krijgt de verkeerde identiteit mee. Een variant is een cache die alleen caseId gebruikt en niet tenantId.
Herstel niet door alleen een retry toe te voegen. Stop de betrokken runs, markeer het resultaat als onbetrouwbaar, invalideer de verdachte cache en onderzoek welke toolcalls of reads de grens kruisten. Als een write-tool betrokken kan zijn, laat een bevoegde persoon de downstreamstatus controleren. Een timeout of opnieuw starten herstelt geen verkeerd geautoriseerde actie.
Wanneer kies je een andere aanpak?
Een gedeelde connection pool kan prima zijn wanneer hij alleen transport deelt en geen cookies, headers of per-run state vasthoudt. Voor risicovolle tools is een server-side policy check altijd nodig, ook als de client zorgvuldig is opgezet. Kies geen agent-MCP-route als een klassieke, sterk getypeerde service-interface voor die handeling eenvoudiger te autoriseren en te testen is.
Wil je sessie-eigendom, toolrechten en recovery voor een bestaand systeem als één contract ontwerpen, dan kun je een freelance AI architect inschakelen. Het doel is niet maximaal veel state delen, maar aantoonbaar weten van wie iedere call is.
Bronnen
- Microsoft Agent Framework, Python release 1.19.0, gepubliceerd 18 september 2026, geraadpleegd 29 september 2026. Beschrijft per-invocation MCP-sessies en identity/origin/ownership-scoping; bewijst geen isolatie van eigen infrastructuur.
- Microsoft Agent Framework, repository, geraadpleegd 29 september 2026. Beschrijft de actuele project- en integratiescope; bewijst geen veilige lokale configuratie.
- Microsoft Learn, Use a toolbox with a hosted agent, bijgewerkt 31 juli 2026, geraadpleegd 29 september 2026. Beschrijft per-request call-ID-forwarding en gescheiden agent-/downstreamidentiteiten; beperkt tot de Foundry Toolbox-context.