Phase1 scan hole fixed: 4 German comment sites in lmcp.lua translated (case-sensitive scan missed them); re-attested GRUEN; dual suite contract anchored (34df8841)
This commit is contained in:
@@ -46,3 +46,25 @@ Begriffe, Logik unverändert, Suite 27/27, Smoke 6/6 identisch zur Baseline.
|
||||
json.lua, Suite)
|
||||
- Baseline vor Phase 1: /root/harvest/phase1_baseline/
|
||||
- Patch: /root/harvest/coder_maxbody_65536.patch; Deploy-Doku: /root/harvest/deploy_coder_65536.md
|
||||
|
||||
## Nachtrag 2026-08-10 (Korrektur: Scan-Loch im German-Scan)
|
||||
|
||||
Beim Review der Phase-2-Suite (konformitaet_dual.py) fiel auf: der German-Scan von
|
||||
Phase 1 ("0 Treffer") war FEHLERHAFT — die dokumentierte Suchmaske lief
|
||||
case-sensitiv, wodurch deutsche Kommentare mit grossem Anfangsbuchstaben
|
||||
(`Fassung`, `Sitzung`, `Methode`, und ein ganzer Block L.66-70: `Erlaubnisliste`,
|
||||
`Werkzeug`, `verhindern`, ...) durchrutschten. Betroffen: lmcp.lua (4 Stellen:
|
||||
L.66-70 Block, L.1082, L.1418-1419).
|
||||
|
||||
Korrektur (operator work, reine Kommentar-UEbersetzung, keine Logik):
|
||||
- L.66-70: Erlaubnisliste-Block -> Englisch
|
||||
- L.1082: `unbekannte Methode` -> `unknown method`
|
||||
- L.1418-1419: SSE/405-Block -> Englisch
|
||||
- Neuer Scan (case-insensitive + erweiterte Wortliste, nur Kommentare): alle
|
||||
*.lua CLEAN. Einziger Treffer war ein False Positive ("die" = englisches Verb).
|
||||
- lmcp.lua sha: 72253549ce1f1a45... -> 4a816a44474e1766f8023aadcce2c76a...
|
||||
- Re-Attest: bullpen-attest --impl lmcp.lua --test konformitaet_2026_07_28.py ->
|
||||
GRUEN, rueckgabe 0, Test-sha unveraendert c90450a6...
|
||||
|
||||
Lehre fuer kuenftige Scans: German-Checks IMMER case-insensitive ausfuehren und
|
||||
die Kommentar-Suche mit einer Funktionswort-Liste (und/oder/nicht/...) ergaenzen.
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
KAMPAGNE (b) DUAL-PROTOKOLL — KICKOFF, SCHRITT 1 @TESTDESIGNER: ERWEITERTE SUITE
|
||||
|
||||
PROBLEM
|
||||
PO-Go für Phase 2 (Dual-Protokoll) ist erteilt. Die bestehende Suite
|
||||
(konformitaet_2026_07_28.py, 27/27, sha256 c90450a6240f133c…, Vertrag) bleibt
|
||||
UNVERAENDERT — sie ist der Regression-Net für die neue Fassung. Phase 2 braucht eine
|
||||
ERWEITERTE, version-aware Suite als neuen Vertrag für den Dual-Modus: alte Fassung
|
||||
(2025-06-18) und neue Fassung (2026-07-28) müssen beide funktionieren, version-routed.
|
||||
Der Vertrag kommt zuerst: die erweiterte Suite wird geschrieben und verankert, BEVOR
|
||||
irgendetwas implementiert wird.
|
||||
|
||||
WHERE
|
||||
Die Arbeitskopie lebt in /root/spec-2026-07-28/ (lmcp.lua, server.lua, json.lua,
|
||||
envelope.lua, versions.lua, konformitaet_2026_07_28.py, SPEC-AUSZUG.md, UEBERGABE.md).
|
||||
Die erweiterte Suite ist eine NEUE Datei neben der alten — die alte wird nicht angefasst.
|
||||
Referenz für das Alt-Verhalten: /root/harvest/_lmcp_ref @ 3409a41 (initialize/ping/
|
||||
logging/setLevel, Session-Auflösung — die alte Fassung lebt heute auf dem Raum-Server).
|
||||
|
||||
TRAPS (Verboten — die plausiblen Fehler)
|
||||
- NICHT die bestehende Suite ändern oder schwächen: konformitaet_2026_07_28.py bleibt
|
||||
byte-identisch (sha256 c90450a6…), der 27er-Bestand muss weiter grün sein. Die neue
|
||||
Suite ist eine NEUE Datei (z.B. konformitaet_dual.py), die die alten 27 Fälle ÜBERNIMMT
|
||||
(kopiert, nicht modifiziert) und die Alt-Fassungs-Fälle HINZUFÜGT.
|
||||
- KEINE Self-Tests im Coder-Teil: die Suite ist der Test.
|
||||
- NICHT nach "Header vorhanden?" routen: die ANGEKÜNDIGTE Version entscheidet
|
||||
(MCP-Protocol-Version-Header ODER _meta["io.modelcontextprotocol/protocolVersion"]).
|
||||
- KEINE Version angekündigt -> ALTE Fassung (2025-06-18), PO-Entscheidung E2 — die
|
||||
Raum-Klienten sprechen 2025-06-18 ohne Header.
|
||||
- Kein Zugriff auf den laufenden Raum-Server: die Suite testet die Arbeitskopie.
|
||||
LMCP_HOST/LMCP_PORT/LMCP_TOKEN gegen einen LOKALEN Testserver aus server.lua.
|
||||
|
||||
ANFORDERUNG (Zieldatei: konformitaet_dual.py im Verzeichnis der Arbeitskopie)
|
||||
Schreibe eine version-aware Conformance-Suite in PYTHON (frisch geschriebener Code auf
|
||||
Englisch). Sie testet gegen einen lokal gestarteten Server (server.lua) via
|
||||
urllib/HTTP wie die bestehende Suite. Abdeckung:
|
||||
|
||||
A) NEUE Fassung (2026-07-28) — alle 27 Verhalten der bestehenden Suite, unverändert
|
||||
übernommen (die 27 Prüfungen der alten Suite als Teilmenge; bei Abweichung ist die
|
||||
Übernahme falsch). Insbesondere: initialize -> -32601/404 (entfernt),
|
||||
Mcp-Method/Header-Pflicht, _meta-Pflicht, Version-Gate unbekannt -> -32022 mit
|
||||
data.supported=['2026-07-28'] (hier: mit BEIDEN Fassungen, s.u.), Keystone
|
||||
"discover enthält 2026-07-28".
|
||||
|
||||
B) ALTE Fassung (2025-06-18) — die Alt-Verhalten, version-routed:
|
||||
1. server/discover mit Header MCP-Protocol-Version: 2025-06-18 -> 200,
|
||||
supportedVersions enthält BEIDE: "2026-07-28" UND "2025-06-18".
|
||||
2. initialize (2025-06-18) -> 200, NICHT -32601 (alte Methode wieder aktiv).
|
||||
3. ping (2025-06-18) -> 200.
|
||||
4. logging/setLevel (2025-06-18, level="debug") -> 200.
|
||||
5. KEINE Envelope-Pflicht für alte Klienten: POST ohne Mcp-Method-Header und ohne
|
||||
MCP-Protocol-Version-Header -> NICHT -32020 (die alte Fassung kennt den
|
||||
Envelope-Check nicht; Default-Routing = alt, E2).
|
||||
6. Unbekannte Version (z.B. 1900-01-01) -> 400, code -32022, data.supported enthält
|
||||
BEIDE Fassungen, data.requested = '1900-01-01'.
|
||||
|
||||
C) KONSISTENZ: derselbe Request mit derselben angekündigten Version muss dasselbe
|
||||
Ergebnis geben, egal ob die Version im Header oder in _meta steht (wo beides
|
||||
zulässig ist).
|
||||
|
||||
Der Testserver-Aufruf erfolgt wie in der alten Suite (LMCP_PORT aus env mit Default,
|
||||
LUA_PATH auf das Arbeitskopie-Verzeichnis, server.lua starten). Der Test ist ein
|
||||
runnahes Skript: `python3 konformitaet_dual.py lmcp.lua` -> Exit 0 und Zähler
|
||||
"X ok, 0 OFFEN" analog zur alten Suite.
|
||||
|
||||
ABNAHME (@testdesigner selbst)
|
||||
- Die Suite LÄUFT gegen die Arbeitskopie, sobald sie implementiert ist — jetzt noch
|
||||
nicht: der Implementierungsstand ist alt (nur 2026-07-28). Lieferung = die neue
|
||||
Datei konformitaet_dual.py (Rohtext, keine Fences) + sha256 + Erklärung, welche
|
||||
Prüfungen übernommen (27) und welche neu (B-Teil) sind.
|
||||
- WICHTIG: die alte Suite muss unverändert bleiben (sha256 c90450a6…) — mitliefern als
|
||||
Beleg.
|
||||
|
||||
CONSTRAINTS
|
||||
- Nur die neue Datei konformitaet_dual.py entsteht. Keine Änderung an lmcp.lua,
|
||||
versions.lua, envelope.lua, server.lua, konformitaet_2026_07_28.py.
|
||||
- Neuer Code auf Englisch (PO-Ziel (a)).
|
||||
- Lieferung als Rohtext im Raum, keine Fences.
|
||||
|
||||
Dispatch, then END YOUR TURN.
|
||||
+10
-10
@@ -63,11 +63,11 @@ function lmcp.new(name, opts)
|
||||
self.host = opts.host or "0.0.0.0"
|
||||
self.port = opts.port or 8080
|
||||
self.tools = {}
|
||||
-- Erlaubnisliste je Instanz (LMCP_TOOL_ALLOW, kommagetrennt). Ist sie
|
||||
-- gesetzt, registriert `tool()` NUR diese Namen -- Built-ins wie Plugins.
|
||||
-- Nicht gesetzt: alles wie bisher. Das ist die einzige Stelle, an der ein
|
||||
-- Werkzeug entsteht, also die einzige, an der man es verhindern kann;
|
||||
-- nachtraeglich loeschen muss jeden kuenftigen Eintrag kennen und veraltet.
|
||||
-- Per-instance allowlist (LMCP_TOOL_ALLOW, comma-separated). When set,
|
||||
-- `tool()` registers ONLY those names -- built-ins like plugins.
|
||||
-- When unset: everything as before. This is the only place where a
|
||||
-- tool is created, hence the only place where it can be prevented;
|
||||
-- removing one later means every future entry must know and stale out.
|
||||
self.tool_allow = nil
|
||||
do
|
||||
local roh = os.getenv("LMCP_TOOL_ALLOW")
|
||||
@@ -1079,7 +1079,7 @@ local _finalise_dispatch
|
||||
-- following parts (header, _meta, version) get their 400s without anyone
|
||||
-- having to add to it again.
|
||||
--
|
||||
-- -32601 unbekannte Methode -> 404 („MUST respond with 404 Not
|
||||
-- -32601 unknown method -> 404 („MUST respond with 404 Not
|
||||
-- Found and … -32601")
|
||||
-- -32020 HeaderMismatch -> 400
|
||||
-- -32021 MissingRequiredClientCapability -> 400
|
||||
@@ -1414,10 +1414,10 @@ local function _conn_read(self, conn)
|
||||
conn.state = "writing"
|
||||
elseif conn.method == "GET" or conn.method == "DELETE" then
|
||||
-- MCP 2026-07-28: „HTTP GET or DELETE to the MCP endpoint: respond
|
||||
-- with 405 Method Not Allowed." Beide trugen die Mechanik der
|
||||
-- alten Fassung — GET oeffnete einen eigenstaendigen SSE-Strom,
|
||||
-- DELETE beendete eine Sitzung. Zitat der Spec zu beidem: „None of
|
||||
-- these mechanisms are part of this revision."
|
||||
-- with 405 Method Not Allowed." Both carried the mechanics of the
|
||||
-- old version — GET opened a separate SSE stream, DELETE ended
|
||||
-- a session. Spec quote for both: „None of these mechanisms are
|
||||
-- part of this revision."
|
||||
conn.write_buf = _build_http_response("405 Method Not Allowed",
|
||||
{ ["Content-Type"] = "text/plain",
|
||||
["Allow"] = "POST, OPTIONS",
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
ENTSCHEIDUNG zu deiner Eskalation #872: OPTION B (Rohtext im Raum).
|
||||
|
||||
Der Inhalt der alten Suite konformitaet_2026_07_28.py (sha256 c90450a6240f133c...)
|
||||
steht unten VERBATIM (234 Zeilen, 10861 Bytes, unveraendert — nachpruefbar per sha256).
|
||||
Re-dispatch an @testdesigner: unveraendert den Auftrag von ask 866, aber mit dieser
|
||||
Quelle als Anlage (Block QUELLTEXT). Hinweis fuer @testdesigner: die 27 Faelle der alten
|
||||
Suite in die neue konformitaet_dual.py UEBERNEHMEN (als Teilmenge, Verhalten exakt gleich;
|
||||
nur Version-Gate-Erwartung data.supported auf BEIDE Fassungen erweitern, weil der
|
||||
Implementierungsstand sich aendert). Zusaetzliche Referenz fuer Alt-Verhalten
|
||||
(initialize/ping/logging/setLevel): die alte Fassung ist in /root/harvest/_lmcp_ref
|
||||
gespiegelt, aber die B-Faelle sind im Auftragstext schon vollstaendig spezifiziert.
|
||||
|
||||
QUELLTEXT (konformitaet_2026_07_28.py, verbatim):
|
||||
#!/usr/bin/env python3
|
||||
"""Conformance suite for MCP 2026-07-28, cut from the specification itself.
|
||||
|
||||
Sources fetched 2026-08-09 and quoted per check:
|
||||
/specification/2026-07-28/ (index)
|
||||
/specification/2026-07-28/basic (messages, _meta, error codes)
|
||||
/specification/2026-07-28/basic/versioning (version negotiation)
|
||||
/specification/2026-07-28/server/discover (DiscoverResult shape)
|
||||
/specification/2026-07-28/basic/transports/streamable-http (headers, statuses)
|
||||
|
||||
NOT derived from the changelog or from any second-hand delta: two field names in
|
||||
the earlier draft were guessed wrong (`protocolVersions` instead of
|
||||
`supportedVersions`, `serverInfo` at top level instead of inside `_meta`).
|
||||
|
||||
The spec normalises BOTH the JSON-RPC error code and the HTTP status, so both
|
||||
are checked. A check that only looks at the body would pass a server that
|
||||
answers 200 where the spec demands 400.
|
||||
|
||||
Expected result on an unmigrated server: almost everything OFFEN. That is the
|
||||
measurement — the distance to the target — not a failure of the suite.
|
||||
|
||||
python3 konf2026.py <pfad/zu/lmcp.lua>
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
ZIEL = "2026-07-28"
|
||||
KLON = (os.path.dirname(os.path.abspath(sys.argv[1])) if len(sys.argv) > 1
|
||||
else "/home/mfritsche/src/lmcp-work")
|
||||
|
||||
_offen = 0
|
||||
def pruefe(punkt, was, ok, beleg=None, bekommen=None):
|
||||
global _offen
|
||||
if not ok:
|
||||
_offen += 1
|
||||
print(f" [{'ok ' if ok else 'OFFEN '}] {punkt:<7} {was}")
|
||||
if not ok and bekommen is not None:
|
||||
print(f" bekommen: {bekommen}")
|
||||
if not ok and beleg:
|
||||
print(f" Spec: {beleg}")
|
||||
|
||||
|
||||
META = {"io.modelcontextprotocol/protocolVersion": ZIEL,
|
||||
"io.modelcontextprotocol/clientCapabilities": {},
|
||||
"io.modelcontextprotocol/clientInfo": {"name": "konf", "version": "1"}}
|
||||
|
||||
|
||||
def freier_port():
|
||||
s = socket.socket(); s.bind(("127.0.0.1", 0)); p = s.getsockname()[1]; s.close(); return p
|
||||
|
||||
|
||||
def ruf(port, token, methode, params=None, kopf=None, meta=META, art="POST"):
|
||||
"""Gibt (jsonrpc-objekt, http-status) zurueck. Beides zaehlt."""
|
||||
p = dict(params or {})
|
||||
if meta is not None:
|
||||
p["_meta"] = meta
|
||||
rumpf = {"jsonrpc": "2.0", "id": 1, "method": methode}
|
||||
if p:
|
||||
rumpf["params"] = p
|
||||
k = {"Content-Type": "application/json",
|
||||
"Accept": "application/json, text/event-stream",
|
||||
"Authorization": "Bearer " + token,
|
||||
"MCP-Protocol-Version": ZIEL,
|
||||
"Mcp-Method": methode}
|
||||
if kopf is not None:
|
||||
for name, wert in kopf.items():
|
||||
if wert is None:
|
||||
k.pop(name, None)
|
||||
else:
|
||||
k[name] = wert
|
||||
req = urllib.request.Request(f"http://127.0.0.1:{port}/mcp",
|
||||
json.dumps(rumpf).encode(), k, method=art)
|
||||
try:
|
||||
a = urllib.request.urlopen(req, timeout=20)
|
||||
roh, status = a.read().decode(), a.status
|
||||
except urllib.error.HTTPError as e:
|
||||
roh, status = e.read().decode(), e.code
|
||||
except Exception as e:
|
||||
return {"_transport": f"{type(e).__name__}: {e}"}, 0
|
||||
for z in roh.splitlines():
|
||||
if z.startswith("data: "):
|
||||
roh = z[6:]; break
|
||||
try:
|
||||
return json.loads(roh), status
|
||||
except Exception:
|
||||
return {"_roh": roh[:160]}, status
|
||||
|
||||
|
||||
def code(a):
|
||||
return (a.get("error") or {}).get("code")
|
||||
|
||||
|
||||
port, token = freier_port(), "konf-2026-07-28"
|
||||
umg = dict(os.environ, LMCP_PORT=str(port), LMCP_TOKEN=token, LMCP_HOST="127.0.0.1",
|
||||
LUA_PATH=f"{KLON}/?.lua;;")
|
||||
srv = subprocess.Popen(["lua5.4", f"{KLON}/server.lua"], cwd=KLON, env=umg,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
try:
|
||||
for _ in range(60):
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
socket.create_connection(("127.0.0.1", port), 1).close(); break
|
||||
except OSError:
|
||||
if srv.poll() is not None:
|
||||
print(" Server startete nicht:", (srv.stderr.read() or b"").decode()[:300])
|
||||
sys.exit(2)
|
||||
else:
|
||||
print(" Server kam nicht hoch"); sys.exit(2)
|
||||
|
||||
print(f" MCP {ZIEL} — Konformitaet von {KLON}\n")
|
||||
|
||||
# ---- server/discover ---------------------------------------------------
|
||||
a, st = ruf(port, token, "server/discover")
|
||||
r = a.get("result") or {}
|
||||
pruefe("discover", "server/discover wird beantwortet", "result" in a,
|
||||
"server/discover: „Servers MUST implement it.\"", json.dumps(a)[:130])
|
||||
pruefe("discover", "result.supportedVersions ist eine nicht-leere Liste",
|
||||
isinstance(r.get("supportedVersions"), list) and bool(r.get("supportedVersions")),
|
||||
"DiscoverResult: supportedVersions", repr(r.get("supportedVersions")))
|
||||
pruefe("discover", "result.capabilities ist ein Objekt",
|
||||
isinstance(r.get("capabilities"), dict), None, repr(r.get("capabilities")))
|
||||
pruefe("discover", "_meta traegt io.modelcontextprotocol/serverInfo",
|
||||
isinstance((r.get("_meta") or {}).get("io.modelcontextprotocol/serverInfo"), dict),
|
||||
"„Servers SHOULD include this field.\"", repr((r.get("_meta") or {}))[:90])
|
||||
pruefe("discover", f"supportedVersions enthaelt {ZIEL}",
|
||||
ZIEL in (r.get("supportedVersions") or []), None,
|
||||
repr(r.get("supportedVersions")))
|
||||
|
||||
# ---- resultType auf JEDEM Ergebnis -------------------------------------
|
||||
for m in ("server/discover", "tools/list"):
|
||||
a, _ = ruf(port, token, m)
|
||||
rt = (a.get("result") or {}).get("resultType")
|
||||
pruefe("result", f"{m}: result.resultType == \"complete\"", rt == "complete",
|
||||
"„The result MUST include a resultType field.\"", repr(rt))
|
||||
|
||||
# ---- _meta ist Pflicht --------------------------------------------------
|
||||
a, st = ruf(port, token, "tools/list", meta=None)
|
||||
pruefe("_meta", "fehlendes _meta -> -32602", code(a) == -32602,
|
||||
"„A request missing any required field is malformed; the server MUST "
|
||||
"reject it with -32602.\"", f"code={code(a)}")
|
||||
pruefe("_meta", "fehlendes _meta -> HTTP 400", st == 400,
|
||||
"„On HTTP, the response status MUST be 400 Bad Request.\"", f"HTTP {st}")
|
||||
|
||||
ohne_caps = {k: v for k, v in META.items()
|
||||
if k != "io.modelcontextprotocol/clientCapabilities"}
|
||||
a, st = ruf(port, token, "tools/list", meta=ohne_caps)
|
||||
pruefe("_meta", "fehlende clientCapabilities -> -32602", code(a) == -32602,
|
||||
"clientCapabilities: Required = Yes", f"code={code(a)}")
|
||||
|
||||
# ---- Fassungsaushandlung ------------------------------------------------
|
||||
schlecht = dict(META, **{"io.modelcontextprotocol/protocolVersion": "1900-01-01"})
|
||||
a, st = ruf(port, token, "tools/list", meta=schlecht,
|
||||
kopf={"MCP-Protocol-Version": "1900-01-01"})
|
||||
pruefe("version", "unbekannte Fassung -> -32022", code(a) == -32022,
|
||||
"„it MUST respond with an UnsupportedProtocolVersionError\"", f"code={code(a)}")
|
||||
pruefe("version", "unbekannte Fassung -> HTTP 400", st == 400,
|
||||
"„MUST respond with 400 Bad Request and an UnsupportedProtocolVersionError\"",
|
||||
f"HTTP {st}")
|
||||
d = (a.get("error") or {}).get("data") or {}
|
||||
pruefe("version", "Fehler nennt data.supported", isinstance(d.get("supported"), list),
|
||||
None, repr(d)[:90])
|
||||
pruefe("version", "Fehler nennt data.requested == angefragte Fassung",
|
||||
d.get("requested") == "1900-01-01",
|
||||
"Beispiel der Spec: data.requested: \"1900-01-01\"", repr(d.get("requested")))
|
||||
|
||||
# ---- Koepfe -------------------------------------------------------------
|
||||
a, st = ruf(port, token, "tools/list", kopf={"Mcp-Method": None})
|
||||
pruefe("header", "fehlender Mcp-Method -> -32020", code(a) == -32020,
|
||||
"„A required standard header … is missing.\" -> HeaderMismatch", f"code={code(a)}")
|
||||
pruefe("header", "fehlender Mcp-Method -> HTTP 400", st == 400,
|
||||
"„servers MUST return HTTP status 400 Bad Request\"", f"HTTP {st}")
|
||||
|
||||
a, st = ruf(port, token, "tools/list", kopf={"Mcp-Method": "prompts/list"})
|
||||
pruefe("header", "Mcp-Method != method -> -32020", code(a) == -32020,
|
||||
"„values specified in the headers do not match … MUST reject\"", f"code={code(a)}")
|
||||
|
||||
a, st = ruf(port, token, "tools/list", kopf={"MCP-Protocol-Version": None})
|
||||
pruefe("header", "fehlender MCP-Protocol-Version -> -32020", code(a) == -32020,
|
||||
"„Every POST request … MUST include an MCP-Protocol-Version header.\"",
|
||||
f"code={code(a)}")
|
||||
|
||||
a, st = ruf(port, token, "tools/list", kopf={"MCP-Protocol-Version": "2025-06-18"})
|
||||
pruefe("header", "Kopf-Fassung != _meta-Fassung -> -32020", code(a) == -32020,
|
||||
"„If the values do not match, the server MUST reject … HeaderMismatch\"",
|
||||
f"code={code(a)}")
|
||||
|
||||
# ---- unbekannte Methode -------------------------------------------------
|
||||
a, st = ruf(port, token, "gibt/esnicht")
|
||||
pruefe("method", "unbekannte Methode -> -32601", code(a) == -32601, None, f"code={code(a)}")
|
||||
pruefe("method", "unbekannte Methode -> HTTP 404", st == 404,
|
||||
"„it MUST respond with 404 Not Found and a JSON-RPC error with code -32601\"",
|
||||
f"HTTP {st}")
|
||||
|
||||
# ---- gestrichene Methoden ----------------------------------------------
|
||||
for m in ("initialize", "ping", "logging/setLevel"):
|
||||
a, _ = ruf(port, token, m)
|
||||
pruefe("entfernt", f"{m} existiert nicht mehr (-32601)", code(a) == -32601,
|
||||
"SEP-2575: entfernt", f"code={code(a)}")
|
||||
|
||||
# ---- verbotene Fehlercodes ---------------------------------------------
|
||||
a, _ = ruf(port, token, "resources/read", {"uri": "file:///gibtsnicht"},
|
||||
kopf={"Mcp-Name": "file:///gibtsnicht"})
|
||||
pruefe("codes", "unbekannte Ressource -> -32602, NICHT -32002", code(a) == -32602,
|
||||
"„Implementations of this protocol version MUST NOT emit … -32002\"",
|
||||
f"code={code(a)}")
|
||||
|
||||
# ---- GET/DELETE auf dem Endpunkt ---------------------------------------
|
||||
for art in ("GET", "DELETE"):
|
||||
try:
|
||||
req = urllib.request.Request(f"http://127.0.0.1:{port}/mcp", method=art,
|
||||
headers={"Authorization": "Bearer " + token})
|
||||
st = urllib.request.urlopen(req, timeout=10).status
|
||||
except urllib.error.HTTPError as e:
|
||||
st = e.code
|
||||
except Exception:
|
||||
st = 0
|
||||
pruefe("transport", f"HTTP {art} auf dem Endpunkt -> 405", st == 405,
|
||||
"„respond with 405 Method Not Allowed\"", f"HTTP {st}")
|
||||
|
||||
print()
|
||||
print(f" {_offen} Punkte OFFEN")
|
||||
sys.exit(1 if _offen else 0)
|
||||
finally:
|
||||
srv.terminate()
|
||||
try:
|
||||
srv.wait(5)
|
||||
except Exception:
|
||||
srv.kill()
|
||||
Reference in New Issue
Block a user