Je client vraagt twee berekeningen op. Het tweede antwoord arriveert als eerste: hoort het dan bij de eerste open plek? Alleen op volgorde vertrouwen zou antwoorden verwisselen; een register koppelt ieder bericht aan zijn eigen verzoek.
Een transport draagt bytes tussen client en server. MCP kent stdio en Streamable HTTP; wij gebruiken een eigen geheugenvervoer met twee begrensde wachtrijen. De gegeven helpers versturen echte UTF-8/JSON-pakketten zonder netwerkpoort, subprocess of HTTP-server.
Bij stdio start de client een serverproces; berichten gaan via stdin en stdout. Op stdout staan alleen protocolberichten. Streamable HTTP gebruikt een HTTP-endpoint: POST verstuurt berichten en GET kan een serverstream openen.
1. Houd ieder verzoek herkenbaar
Schrijf track_mcp_request voor een nieuw register met gebruikte, openstaande en afgehandelde IDs. Pending betekent nog wachtend op afhandeling. Geef steeds een nieuwe registersnapshot terug; de oorspronkelijke toestand blijft zo controleerbaar.
Een MCP-request-ID wordt door dezelfde verzoeker binnen dezelfde sessie niet opnieuw gebruikt, ook na voltooiing. Dat verschilt van de stabiele hostactie-ID uit het offerteproject: die voorkwam dubbele domeineffecten. Het protocol-ID correleert berichten en levert op zichzelf geen idempotentiegarantie.
def ask(i):
return {'jsonrpc':'2.0', 'id':i,
'method':'ping'}
registry = new_mcp_registry()
registry = track_mcp_request(
registry, ask(0))
registry = track_mcp_request(
registry, ask('0'))
print([p['id'] for p in
registry['pending']])
# [0, '0']
Het getal 0 en de tekst "0" zijn verschillende sleutels. De numerieke vormen 0 en 0.0 horen wel bij dezelfde correlatie; booleans en fractionele IDs worden afgewezen. De gegeven codec bewaakt ook precisieverlies in oorspronkelijke getaltokens.
Onze geheugenlimieten zijn onderwijskeuzes: maximaal zestien openstaande verzoeken en 64 gebruikte IDs per register. De host kiest bij opnieuw verbinden een verse sessiegeneratie, epoch genoemd. Die generatie en de richting staan buiten de MCP-payload en worden niet door een modelantwoord bepaald.
Schrijf receive_mcp_response voor het ontvangen pakket. Controleer eerst de eigen richting en generatie; zoek daarna de passende pending-ID. Een onbekend, dubbel of reeds afgehandeld antwoord maakt geen nieuwe afhandeling aan.
TWEE VERZOEKEN · TWEE VERSCHILLENDE IDSHet volgende antwoord hoeft niet bij het eerste verzoek te horen
Getal 0 en tekst "0" hebben ieder een eigen wachtende rij. Numeriek 0.0 hoort bij getal 0.
Beide verzoeken wachtenpending
Wachtend
ID 0ping
ID "0"ping
Afgehandeld
Nog geen antwoorden afgehandeld.
Gebruikte IDs
2
Generatie
1
Verstuurd in deze verbinding
2
Werkelijke verzendbytes
82
Een ID blijft gebruikt nadat het verzoek is afgehandeld. Annuleren stopt het lokale wachten en belooft geen rollback.
Deze hulpfunctie gebruikt de gegeven bytewachtrij. We geven het tekstverzoek eerst antwoord, daarna het numerieke verzoek. De eenvoudige result-objecten zijn hier alleen correlatiegegevens; een toolprocedure moet haar eigen outputschema daarnaast controleren.
link = new_mcp_link()
def deliver(i, value):
msg = {'jsonrpc':'2.0', 'id':i,
'result':{'value':value}}
queue_mcp_message(
link, 'server-to-client', msg)
return receive_mcp_packet(
link, 'server-to-client')
for i, value in [('0', 20), (0.0, 10)]:
trial = receive_mcp_response(
registry, deliver(i, value))
registry = trial['state']
print([r['id'] for r in
registry['settled']])
# ['0', 0]
Er waren twee openstaande verzoeken. Na twee nieuw gematchte antwoorden blijven er nul over; een dubbel antwoord zou niets extra aftrekken. Noem het beginaantal P₀, het aantal nieuw geaccepteerde antwoorden A en het resterende aantal P:
Deze telling geldt voor die twee succesvolle afhandelingen zonder annulering. Een veelgemaakte fout is een laat pakket uit een oude verbinding toch als antwoord te gebruiken. Hieronder bestaat ID 0 opnieuw, maar in generatie 2; een al eerder verzonden pakket uit generatie 1 past daar niet bij.
Schrijf cancel_mcp_request voor een eigen nog openstaand request. De functie maakt een notifications/cancelled-melding en handelt het verzoek lokaal af. Een later antwoord wordt vervolgens genegeerd; dat bewijst niet dat de server een al uitgevoerde bewerking heeft teruggedraaid.
live = track_mcp_request(fresh, ask(5))
cancel = cancel_mcp_request(live, 5)
print(cancel['notification']['method'])
late = receive_mcp_response(
cancel['state'], deliver(5, 7))
print(late['disposition'])
# notifications/cancelled
# ignored-settled-id
Initialize wordt niet zo gecanceld; daarvoor gebruikte je de initialisatietimeout. Task-augmented requests hebben een ander cancelmechanisme en worden in ons profiel vóór registratie geweigerd. Client en server kunnen zelf verzoeker zijn; de eigen reden volgt daarom de verzoeker.
Als een MCP-fout geen ID bevat, koppelt onze client die niet aan zomaar één request. Ons sessiebeleid sluit dan het register en handelt alle pending verzoeken eenmaal als sessiefout af. De nog wachtende ID 0 uit de vorige proef verdwijnt daardoor ook:
Je uitvoer verschijnt hier. Alles rekent in je eigen browser.
Jouw checkpoints0 / 3
01
Registreer ieder ID éénmaal
Schrijf track_mcp_request(registry, message) met de gegeven register- en envelopehelpers: alleen een request in een open register mag worden geregistreerd, iedere aanwezigheid van params.task is buiten het profiel. Bewaar een onafhankelijke snapshot met ID in used en exact id/method in pending; vergelijk numerieke en tekst-IDs getypeerd en gebruik een eerder gebruikt ID binnen dezelfde sessie nooit opnieuw. Bewaak maximaal 64 used en 16 pending, wijs ongeldige invoer/hergebruik/limieten met ValueError af en laat de oorspronkelijke toestand intact.
Een hint
Ook na ontvangst of annulering blijft het ID in used; een verse sessiegeneratie heeft een eigen register.
02
Verwerk het juiste antwoord
Schrijf receive_mcp_response(registry, packet) voor de gegeven transportwrapper epoch/direction/payload: ongeldige register-/wrappervorm, metadata of payload boven 8192 bytes geeft ValueError; geef state, accepted en disposition als onafhankelijke registersnapshot. Gesloten register, verkeerde richting, oude epoch, geldig niet-responsebericht, onbekende ID of al afgehandelde ID geven accepted False en respectievelijk ignored-closed/ignored-direction/ignored-epoch/ignored-non-response/ignored-unknown-id/ignored-settled-id; controleer richting en epoch vóór decoding. Bij een geldige wrapper sluit een ondecodeerbare MCP-payload alle pending eenmaal via de gegeven helper met invalid-packet en closed-invalid-packet, error zonder ID doet dat met uncorrelated-error en closed-uncorrelated-error; anders matcht een result/error het getypeerde pending-ID, verwijdert die rij, bewaart id/status/message/reason(None) in settled en geeft accepted True met settled, zonder het originele register of pakket te muteren.
Een hint
De verwachte ontvangstrichting volgt de requester; accepted betekent hier een nieuw gecorreleerd antwoord, geen bewijs van domeinjuistheid.
03
Annuleer zonder ID te hergebruiken
Schrijf cancel_mcp_request(registry, request_id): valideer register en getypeerd ID en geef state, notification en disposition. Gesloten of niet-pending geeft de ongewijzigde snapshot, notification None en not-pending; een pending initialize geeft ValueError. Andere eigen pending requests worden verwijderd en eenmaal als cancelled met message None en reason requester+'-cancelled' bewaard; geef cancel-requested en een notifications/cancelled-melding zonder eigen ID, met het oorspronkelijke requestId en een begrensde reden, behoud used en alle invoer en suggereer geen rollback.
Een hint
Annuleren beëindigt de lokale wachttoestand; een later antwoord is al afgehandeld en wordt genegeerd.
Vastgelopen? Bekijk de voorbeeldoplossing
import copy
import json
import math
import re
from decimal import Decimal, InvalidOperation
MCP_VERSION = '2025-11-25'
MCP_MAX_BYTES = 8192
MCP_INPUT_SCHEMA = {
'type':'object',
'properties':{'a':{'type':'integer','minimum':0,'maximum':1000},
'b':{'type':'integer','minimum':0,'maximum':1000}},
'required':['a','b'], 'additionalProperties':False}
MCP_OUTPUT_SCHEMA = {
'type':'object',
'properties':{'value':{'type':'integer','minimum':0,'maximum':1000000},
'status':{'type':'string','enum':['ok','invalid-arguments','limit-exceeded']}},
'required':['value','status'], 'additionalProperties':False}
def _mcp_integer(value, low, high):
if type(value) not in (int,float) or not low <= value <= high or not math.isfinite(value) or value != int(value):
raise ValueError('Een begrensde JSON-integerwaarde vereist; bool is geen getal')
return int(value)
def mcp_id_key(identifier):
"""Numeric 1 and 1.0 correlate; string '1' is a different ID."""
if type(identifier) is str:
if not re.fullmatch(r'[A-Za-z0-9_-]{1,40}',identifier):
raise ValueError('ID-tekst buiten de oefengrens')
return ('string',identifier)
return ('number',_mcp_integer(identifier,-1000000,1000000))
def _mcp_json_tree(value):
remaining = [512]
active = set()
def visit(item, depth):
remaining[0] -= 1
if remaining[0] < 0 or depth > 8:
raise ValueError('JSON-boom buiten de oefengrens')
if item is None or type(item) is bool:
return
if type(item) is str:
if len(item) > 4000:
raise ValueError('JSON-tekst te lang')
return
if type(item) in (int,float):
if abs(item) > 1000000000 or not math.isfinite(item):
raise ValueError('JSON-getal buiten de oefengrens')
return
if type(item) not in (dict,list) or id(item) in active:
raise ValueError('Alleen eindige JSON-bomen')
active.add(id(item))
if type(item) is dict:
if len(item) > 32 or any(type(key) is not str or len(key) > 128 for key in item):
raise ValueError('JSON-object buiten de oefengrens')
for key, child in item.items():
visit(key,depth+1); visit(child,depth+1)
else:
if len(item) > 64:
raise ValueError('JSON-lijst te lang')
for child in item:
visit(child,depth+1)
active.remove(id(item))
visit(value,0)
def _mcp_json_bytes(value):
_mcp_json_tree(value)
try:
payload = json.dumps(value,ensure_ascii=False,allow_nan=False,separators=(',',':')).encode('utf-8')
except (ValueError,UnicodeError) as error:
raise ValueError('Geen geldige UTF-8/JSON') from error
if len(payload) > MCP_MAX_BYTES:
raise ValueError('Pakket groter dan de oefengrens van8192bytes')
return payload
def _mcp_parse_json(text):
def pairs(items):
result = {}
for key,value in items:
if key in result:
raise ValueError('Dubbele JSON-objectsleutel')
result[key] = value
return result
def constant(value):
raise ValueError('Geen NaN of Infinity in JSON')
def decimal_number(token):
# Reject a lossy wire conversion rather than accepting a fractional ID
# or schema value as an integer after binary-float rounding/underflow.
try:
exact = Decimal(token)
number = float(token)
if not math.isfinite(number) or Decimal(str(number)) != exact:
raise ValueError('Wiregetal verliest decimale precisie in de oefenruntime')
return number
except (InvalidOperation,OverflowError,ValueError) as error:
raise ValueError('Wiregetal buiten de precieze oefenvorm') from error
try:
return json.loads(text,object_pairs_hook=pairs,parse_constant=constant,parse_float=decimal_number)
except (ValueError,RecursionError) as error:
raise ValueError('Ongeldige JSON') from error
def mcp_envelope(message):
"""Validate and copy the selected envelope shapes; no method authority."""
clean = _mcp_parse_json(_mcp_json_bytes(message).decode('utf-8'))
if type(clean) is not dict or clean.get('jsonrpc') != '2.0':
raise ValueError('Eén JSON-RPC2.0-object vereist')
fields = set(clean)
if 'method' in clean:
if not {'jsonrpc','method'} <= fields <= {'jsonrpc','method','id','params'}:
raise ValueError('Exact request-/notification-envelope')
if type(clean['method']) is not str or not re.fullmatch(r'[A-Za-z][A-Za-z0-9_/.-]{0,79}',clean['method']):
raise ValueError('Methode buiten de oefengrens')
if 'params' in clean and type(clean['params']) is not dict:
raise ValueError('MCP-params is een object')
if '_meta' in clean.get('params',{}):
validate_mcp_meta(clean['params']['_meta'],request=True)
if 'id' in clean:
mcp_id_key(clean['id'])
elif 'result' in clean:
if fields != {'jsonrpc','id','result'} or type(clean['result']) is not dict:
raise ValueError('Result-response heeft ID en objectresultaat')
mcp_id_key(clean['id'])
if '_meta' in clean['result']:
validate_mcp_meta(clean['result']['_meta'])
elif 'error' in clean:
if not {'jsonrpc','error'} <= fields <= {'jsonrpc','id','error'}:
raise ValueError('Exact error-envelope vereist')
if 'id' in clean:
mcp_id_key(clean['id'])
error = clean['error']
if type(error) is not dict or not {'code','message'} <= set(error) <= {'code','message','data'}:
raise ValueError('Error heeft code/message en eventueel data')
_mcp_integer(error['code'],-1000000,1000000)
if type(error['message']) is not str or not error['message'].strip() or len(error['message']) > 2000:
raise ValueError('Begrensde foutmelding vereist')
else:
raise ValueError('Request, notification, result of error vereist')
return clean
def mcp_message_kind(message):
clean = mcp_envelope(message)
if 'method' in clean:
return 'request' if 'id' in clean else 'notification'
return 'result' if 'result' in clean else 'error'
def encode_mcp_packet(message):
return _mcp_json_bytes(mcp_envelope(message))
def decode_mcp_packet(payload):
if type(payload) is not bytes or not 1 <= len(payload) <= MCP_MAX_BYTES:
raise ValueError('Eén begrensd bytespakket vereist')
try:
text = payload.decode('utf-8')
except UnicodeError as error:
raise ValueError('Pakket is geen UTF-8') from error
return mcp_envelope(_mcp_parse_json(text))
def validate_fixed_mcp_schema(schema,value):
"""Only these two exact classroom schemas, never arbitrary JSON Schema."""
try:
signature = json.dumps(schema,sort_keys=True,allow_nan=False,separators=(',',':'))
except (TypeError,ValueError,RecursionError) as error:
raise ValueError('Geen ondersteund oefenschema') from error
input_signature = json.dumps(MCP_INPUT_SCHEMA,sort_keys=True,separators=(',',':'))
output_signature = json.dumps(MCP_OUTPUT_SCHEMA,sort_keys=True,separators=(',',':'))
if type(value) is not dict:
raise ValueError('Schemas hebben een object als wortel')
if signature == input_signature:
if set(value) != {'a','b'}:
raise ValueError('Exact a en b vereist')
return {key:_mcp_integer(value[key],0,1000) for key in ['a','b']}
if signature == output_signature:
if set(value) != {'value','status'} or type(value['status']) is not str or value['status'] not in ['ok','invalid-arguments','limit-exceeded']:
raise ValueError('Exact value en bekende status vereist')
return {'value':_mcp_integer(value['value'],0,1000000),'status':value['status']}
raise ValueError('Onbekend dialect/keyword/schema: niet gevalideerd')
def validate_mcp_peer(info):
if type(info) is not dict or not {'name','version'} <= set(info):
raise ValueError('Peer heeft name en version')
if any(type(info[key]) is not str or not info[key].strip() or len(info[key]) > 80 for key in ['name','version']):
raise ValueError('Begrensde peeridentificatie vereist')
info = _mcp_parse_json(_mcp_json_bytes(info).decode('utf-8'))
for key in ['title','description','websiteUrl']:
if key in info and (type(info[key]) is not str or len(info[key]) > 2000):
raise ValueError('Optionele peerbeschrijving is begrensde tekst')
if 'icons' in info:
if type(info['icons']) is not list or len(info['icons']) > 16:
raise ValueError('Begrensde iconmetadata vereist; er wordt niets opgehaald')
for icon in info['icons']:
if type(icon) is not dict or 'src' not in icon or type(icon['src']) is not str:
raise ValueError('Iconmetadata heeft een tekstbron')
if 'mimeType' in icon and type(icon['mimeType']) is not str:
raise ValueError('MIME-metadata is tekst')
if 'sizes' in icon and (type(icon['sizes']) is not list or any(type(size) is not str for size in icon['sizes'])):
raise ValueError('Icongroottes zijn tekstwaarden')
if 'theme' in icon and icon['theme'] not in ['light','dark']:
raise ValueError('Bekend iconthema vereist')
return info
def validate_mcp_capabilities(capabilities,role='server'):
if type(capabilities) is not dict or any(type(value) is not dict for value in capabilities.values()):
raise ValueError('Capabilitynamen verwijzen naar objecten, ook een leeg object')
capabilities = _mcp_parse_json(_mcp_json_bytes(capabilities).decode('utf-8'))
client = {'experimental':'object-map','roots':{'listChanged':'bool'},
'sampling':{'context':'object','tools':'object'},'elicitation':{'form':'object','url':'object'},
'tasks':{'list':'object','cancel':'object','requests':{
'sampling':{'createMessage':'object'},'elicitation':{'create':'object'}}}}
server = {'experimental':'object-map','logging':'object','completions':'object',
'prompts':{'listChanged':'bool'},'resources':{'subscribe':'bool','listChanged':'bool'},
'tools':{'listChanged':'bool'},
'tasks':{'list':'object','cancel':'object','requests':{'tools':{'call':'object'}}}}
if role not in ['client','server']:
raise ValueError('Client- of servercapabilities vereist')
def check(data,shape):
for key,kind in shape.items():
if key not in data:
continue
value = data[key]
if kind == 'bool':
if type(value) is not bool:
raise ValueError('Bekende capabilityvlag is een boolean')
elif type(value) is not dict:
raise ValueError('Bekende capabilitystructuur is een object')
elif kind == 'object-map':
if any(type(item) is not dict for item in value.values()):
raise ValueError('Experimentele capabilitywaarden zijn objecten')
elif type(kind) is dict:
check(value,kind)
check(capabilities,client if role == 'client' else server)
# Unknown extensions remain bounded inert data; no automatic use/authority.
return capabilities
def validate_mcp_meta(metadata,request=False):
if type(metadata) is not dict:
raise ValueError('Metadata is een object')
metadata = _mcp_parse_json(_mcp_json_bytes(metadata).decode('utf-8'))
if request and 'progressToken' in metadata and type(metadata['progressToken']) not in (str,int,float):
raise ValueError('Een voortgangstoken is tekst of een getal, geen boolean')
return metadata
def validate_mcp_versions(versions):
if type(versions) not in (list,tuple) or not 1 <= len(versions) <= 4:
raise ValueError('1–4 eigen ondersteunde versies vereist')
if any(type(value) is not str or not value.strip() or len(value) > 40 for value in versions) or len(set(versions)) != len(versions):
raise ValueError('Unieke begrensde versieteksten vereist')
return list(versions)
def validate_mcp_initialize(request):
request = mcp_envelope(request)
if mcp_message_kind(request) != 'request' or request['method'] != 'initialize':
raise ValueError('Initialize-request vereist')
params = request.get('params')
required = {'protocolVersion','capabilities','clientInfo'}
if type(params) is not dict or not required <= set(params) <= required | {'_meta'}:
raise ValueError('Gekozen initialize-params vereist')
validate_mcp_versions([params['protocolVersion']])
validate_mcp_capabilities(params['capabilities'],role='client')
validate_mcp_peer(params['clientInfo'])
if '_meta' in params:
validate_mcp_meta(params['_meta'],request=True)
return request
def mcp_tool_definition():
return {'name':'multiply','description':'Vermenigvuldig twee eigen oefengetallen; productlimiet5000.',
'inputSchema':copy.deepcopy(MCP_INPUT_SCHEMA),'outputSchema':copy.deepcopy(MCP_OUTPUT_SCHEMA),
'annotations':{'readOnlyHint':True,'destructiveHint':False}}
def mcp_tool_result(value,status='ok'):
structured = validate_fixed_mcp_schema(MCP_OUTPUT_SCHEMA,{'value':value,'status':status})
if status != 'ok' and structured['value'] != 0:
raise ValueError('Eigen foutresultaat gebruikt waarde0')
return {'content':[{'type':'text','text':json.dumps(structured,separators=(',',':'))}],
'structuredContent':structured,'isError':status != 'ok'}
def validate_mcp_tool_result(result):
"""Selected text+structured result, including the tool's error branch."""
if type(result) is not dict or not {'content','structuredContent'} <= set(result) <= {'content','structuredContent','isError'}:
raise ValueError('Exact ondersteund toolresultaat vereist')
structured = validate_fixed_mcp_schema(MCP_OUTPUT_SCHEMA,result['structuredContent'])
is_error = result.get('isError',False)
if type(is_error) is not bool or is_error != (structured['status'] != 'ok'):
raise ValueError('isError en eigen status spreken elkaar tegen')
if is_error and structured['value'] != 0:
raise ValueError('Eigen foutresultaat gebruikt waarde0')
content = result['content']
if type(content) is not list or len(content) != 1 or type(content[0]) is not dict or set(content[0]) != {'type','text'}:
raise ValueError('Eén gegeven tekstkopie vereist')
if content[0]['type'] != 'text' or type(content[0]['text']) is not str or len(content[0]['text']) > 4000:
raise ValueError('Begrensde tekstkopie vereist')
mirrored = validate_fixed_mcp_schema(MCP_OUTPUT_SCHEMA,_mcp_parse_json(content[0]['text']))
if mirrored != structured:
raise ValueError('Tekst en gestructureerde data verschillen')
return copy.deepcopy({**result,'isError':is_error,'structuredContent':structured})
def _mcp_epoch(epoch):
if type(epoch) is not int or not 1 <= epoch <= 1000000:
raise ValueError('Eigen sessiegeneratie is een gewone int1–1000000')
return epoch
def _mcp_packet(packet):
if type(packet) is not dict or set(packet) != {'epoch','direction','payload'}:
raise ValueError('Exact transportpakket vereist')
_mcp_epoch(packet['epoch'])
if type(packet['direction']) is not str or packet['direction'] not in ['client-to-server','server-to-client']:
raise ValueError('Bekende transportrichting vereist')
if type(packet['payload']) is not bytes or len(packet['payload']) > MCP_MAX_BYTES:
raise ValueError('Begrensde bytespayload vereist')
return packet
def new_mcp_link(epoch=1):
return {'epoch':_mcp_epoch(epoch),'closed':False,'to_server':[],'to_client':[],
'sent_messages':0,'sent_bytes':0,'delivered_packets':0,'delivered_bytes':0,
'dropped_packets':0,'dropped_bytes':0}
def validate_mcp_link(link):
fields={'epoch','closed','to_server','to_client','sent_messages','sent_bytes',
'delivered_packets','delivered_bytes','dropped_packets','dropped_bytes'}
if type(link) is not dict or set(link) != fields or type(link['closed']) is not bool:
raise ValueError('Exacte interne transporttoestand vereist')
_mcp_epoch(link['epoch'])
for key in ['sent_messages','delivered_packets','dropped_packets']:
if type(link[key]) is not int or not 0 <= link[key] <= 128:
raise ValueError('Transportaantal buiten oefenlimiet')
for key in ['sent_bytes','delivered_bytes','dropped_bytes']:
if type(link[key]) is not int or not 0 <= link[key] <= 128*MCP_MAX_BYTES:
raise ValueError('Transportbytes buiten oefenlimiet')
queued=queued_bytes=0
for key,direction in [('to_server','client-to-server'),('to_client','server-to-client')]:
queue=link[key]
if type(queue) is not list or len(queue)>16:
raise ValueError('Maximaal16pakketten per wachtrij')
for packet in queue:
_mcp_packet(packet)
if packet['epoch'] != link['epoch'] or packet['direction'] != direction:
raise ValueError('Pakket hoort bij deze generatie en richting')
queued+=1;queued_bytes+=len(packet['payload'])
if link['sent_messages'] != link['delivered_packets']+link['dropped_packets']+queued:
raise ValueError('Transportaantallen sluiten niet aan')
if link['sent_bytes'] != link['delivered_bytes']+link['dropped_bytes']+queued_bytes:
raise ValueError('Transportbytes sluiten niet aan')
if link['closed'] and queued:
raise ValueError('Gesloten transport heeft lege wachtrijen')
return link
def queue_mcp_message(link,direction,message):
validate_mcp_link(link)
if type(direction) is not str or direction not in ['client-to-server','server-to-client']:
raise ValueError('Bekende richting vereist')
key='to_server' if direction=='client-to-server' else 'to_client'
if link['closed'] or len(link[key])==16 or link['sent_messages']==128:
raise ValueError('Transport gesloten of begrensde wachtrij vol')
payload=encode_mcp_packet(message)
packet={'epoch':link['epoch'],'direction':direction,'payload':payload}
link[key].append(packet)
link['sent_messages']+=1;link['sent_bytes']+=len(payload)
return copy.deepcopy(packet)
def receive_mcp_packet(link,direction):
validate_mcp_link(link)
if type(direction) is not str or direction not in ['client-to-server','server-to-client']:
raise ValueError('Bekende richting vereist')
key='to_server' if direction=='client-to-server' else 'to_client'
if not link[key]:
return None
packet=link[key].pop(0)
link['delivered_packets']+=1;link['delivered_bytes']+=len(packet['payload'])
return copy.deepcopy(packet)
def close_mcp_link(link):
validate_mcp_link(link)
for key in ['to_server','to_client']:
link['dropped_packets']+=len(link[key])
link['dropped_bytes']+=sum(len(packet['payload']) for packet in link[key])
link[key]=[]
link['closed']=True
return copy.deepcopy(link)
def new_mcp_registry(epoch=1,requester='client'):
if type(requester) is not str or requester not in ['client','server']:
raise ValueError('Bekende verzoeker vereist')
return {'epoch':_mcp_epoch(epoch),'requester':requester,'phase':'open',
'used':[],'pending':[],'settled':[],'reason':None}
def validate_mcp_registry(registry):
if type(registry) is not dict or set(registry) != {'epoch','requester','phase','used','pending','settled','reason'}:
raise ValueError('Exact eigen correlatieregister vereist')
_mcp_epoch(registry['epoch'])
if type(registry['requester']) is not str or registry['requester'] not in ['client','server'] or type(registry['phase']) is not str or registry['phase'] not in ['open','closed']:
raise ValueError('Bekende verzoeker en registerfase vereist')
for key,limit in [('used',64),('pending',16),('settled',64)]:
if type(registry[key]) is not list or len(registry[key])>limit:
raise ValueError('Registerlijst buiten de oefengrens')
used=[mcp_id_key(identifier) for identifier in registry['used']]
if len(set(used))!=len(used):
raise ValueError('Request-ID nooit hergebruiken binnen deze sessie')
active=[];settled=[]
for row in registry['pending']:
if type(row) is not dict or set(row)!={'id','method'} or type(row['method']) is not str or not re.fullmatch(r'[A-Za-z][A-Za-z0-9_/.-]{0,79}',row['method']):
raise ValueError('Pending record heeft ID en methode')
active.append(mcp_id_key(row['id']))
for row in registry['settled']:
if type(row) is not dict or set(row)!={'id','status','message','reason'} or type(row['status']) is not str or row['status'] not in ['result','error','cancelled','session-closed']:
raise ValueError('Bekende eigen afhandelingsrij vereist')
key=mcp_id_key(row['id']);settled.append(key)
if row['status'] in ['result','error']:
message=mcp_envelope(row['message'])
if mcp_message_kind(message)!=row['status'] or 'id' not in message or mcp_id_key(message['id'])!=key or row['reason'] is not None:
raise ValueError('Antwoord hoort bij de afgehandelde ID en status')
elif row['message'] is not None or type(row['reason']) is not str or not row['reason'].strip() or len(row['reason'])>200:
raise ValueError('Lokale afhandeling heeft een begrensde reden')
combined=active+settled
if len(set(combined))!=len(combined) or set(combined)!=set(used):
raise ValueError('Iedere gebruikte ID staat precies eenmaal pending of afgehandeld')
reason=registry['reason']
if registry['phase']=='open':
if reason is not None:raise ValueError('Open register heeft geen sluitreden')
elif registry['pending'] or type(reason) is not str or not reason.strip() or len(reason)>200:
raise ValueError('Gesloten register heeft geen pending en wel een reden')
return copy.deepcopy(registry)
def close_mcp_registry(registry,reason):
registry=validate_mcp_registry(registry)
if type(reason) is not str or not reason.strip() or len(reason)>200:
raise ValueError('Begrensde sluitreden vereist')
if registry['phase']=='closed':
return registry
for row in registry['pending']:
registry['settled'].append({'id':row['id'],'status':'session-closed','message':None,'reason':reason})
registry.update(phase='closed',reason=reason,pending=[])
return registry
def track_mcp_request(registry,message):
registry=validate_mcp_registry(registry)
message=mcp_envelope(message)
if registry['phase']!='open' or mcp_message_kind(message)!='request':
raise ValueError('Registreer alleen requests in een open sessie')
if 'task' in message.get('params',{}):
raise ValueError('Task-augmentation valt buiten dit oefenprofiel')
key=mcp_id_key(message['id'])
if key in [mcp_id_key(identifier) for identifier in registry['used']] or len(registry['used'])==64 or len(registry['pending'])==16:
raise ValueError('ID hergebruikt of begrensd register vol')
registry['used'].append(message['id'])
registry['pending'].append({'id':message['id'],'method':message['method']})
return registry
def receive_mcp_response(registry,packet):
registry=validate_mcp_registry(registry)
packet=_mcp_packet(packet)
def outcome(state,accepted,disposition):
return {'state':state,'accepted':accepted,'disposition':disposition}
if registry['phase']=='closed':
return outcome(registry,False,'ignored-closed')
expected='server-to-client' if registry['requester']=='client' else 'client-to-server'
if packet['direction']!=expected:
return outcome(registry,False,'ignored-direction')
if packet['epoch']!=registry['epoch']:
return outcome(registry,False,'ignored-epoch')
try:
message=decode_mcp_packet(packet['payload'])
except ValueError:
return outcome(close_mcp_registry(registry,'invalid-packet'),False,'closed-invalid-packet')
kind=mcp_message_kind(message)
if kind not in ['result','error']:
return outcome(registry,False,'ignored-non-response')
if 'id' not in message:
return outcome(close_mcp_registry(registry,'uncorrelated-error'),False,'closed-uncorrelated-error')
key=mcp_id_key(message['id'])
matches=[i for i,row in enumerate(registry['pending']) if mcp_id_key(row['id'])==key]
if not matches:
disposition='ignored-settled-id' if key in [mcp_id_key(identifier) for identifier in registry['used']] else 'ignored-unknown-id'
return outcome(registry,False,disposition)
row=registry['pending'].pop(matches[0])
registry['settled'].append({'id':row['id'],'status':kind,'message':message,'reason':None})
return outcome(registry,True,'settled')
def cancel_mcp_request(registry,request_id):
registry=validate_mcp_registry(registry)
key=mcp_id_key(request_id)
matches=[i for i,row in enumerate(registry['pending']) if mcp_id_key(row['id'])==key]
if registry['phase']=='closed' or not matches:
return {'state':registry,'notification':None,'disposition':'not-pending'}
row=registry['pending'][matches[0]]
if row['method']=='initialize':
raise ValueError('Initialize niet cancelen; gebruik de initialisatietimeout')
registry['pending'].pop(matches[0])
registry['settled'].append({'id':row['id'],'status':'cancelled','message':None,'reason':registry['requester']+'-cancelled'})
notification={'jsonrpc':'2.0','method':'notifications/cancelled',
'params':{'requestId':row['id'],'reason':'Eigen oefenverzoek gestopt'}}
return {'state':registry,'notification':notification,'disposition':'cancel-requested'}
link = new_mcp_link()
registry = new_mcp_registry()
request = {'jsonrpc':'2.0','id':0,'method':'ping'}
registry = track_mcp_request(registry,request)
queue_mcp_message(link,'client-to-server',request)
received = decode_mcp_packet(receive_mcp_packet(link,'client-to-server')['payload'])
queue_mcp_message(link,'server-to-client',{'jsonrpc':'2.0','id':received['id'],'result':{}})
packet = receive_mcp_packet(link,'server-to-client')
delivery = receive_mcp_response(registry,packet)
registry = delivery['state']
print(delivery['accepted'],len(registry['pending']),len(registry['settled']))
print(link['sent_messages'],link['sent_bytes'])
Voer de code uit en verklaar voor jezelf waarom elke stap werkt.