Blog • MCP / TypeScript SDK
Nach dem Upgrade auf MCP TypeScript SDK 2.4.0: welche JSON-Schicht soll Schema bei tools/call structuredContent prüfen?
Am 23. Oktober 2026 lieferte das MCP TypeScript SDK @modelcontextprotocol/server 2.4.0. Unter Protokollrevision 2025-11-25 liegen Felder von structuredContent bei Tools mit z.object().meta({ id }) direkt an der Wurzel, ohne result-Hülle.
Viele Audit-Skripte validieren noch structuredContent.result; nach dem Client-Upgrade werden gültige Antworten rot. Ältere Server oder nicht neu gestartete Prozesse liefern weiter legacy mit result. protocol und metaIdTool auf dem Audit-Datensatz wählen validate_root oder legacy_wrapper.
MCP-Verbindung und Credential-Grenzen stehen in MCP-Sicherheit. Schema-gültig reicht nicht, siehe Schema reicht nicht. Bedrock Structured Output ist ein anderer Umschlag, in Bedrock Structured Output. Dieser Artikel behandelt nur die Schema-Schicht nach 2.4.0 und die Gate-Verzweigung.
- 2.4.0-Clients unter 2025-11-25 erwarten bei meta-id-Tools die structuredContent-Wurzel als Output-Payload.
- Pipelines, die nur .result prüfen, melden nach dem Upgrade Fehlalarme; zuerst die Antwortform lesen.
- Legacy-Server können nur result liefern; das Gate soll legacy_wrapper zurückgeben und das Objekt in result prüfen.
- Vermeiden Sie $id-URIs wie my_tool:output im Output-JSON-Schema; Ajv 3.1.8 mit fast-uri lehnt ab—Unterstriche oder $defs.
Zuerst dies: 2.4.0 ändert die structuredContent-Form für meta-id-Tools unter 2025-11-25, nicht tools/list selbst. Unten: Release, Unwrap-Vergleich, Audit-Datensatz, $id-Fallen, Gate-Rückgaben und der JSONNote-Vergleich der zwei JSON-Schichten.
2.4.0 unwrappt structuredContent für meta-id-Tools unter 2025-11-25
Änderung und Migration stehen in Release @modelcontextprotocol/server 2.4.0. Das TypeScript SDK fügt clientseitig kein result mehr für Output-Schemas mit meta id ein; liefert der Server weiter nur result, muss das Audit legacy_wrapper erkennen.
Dieselbe Version verschärft Ajv: fast-uri 3.1.8 parst $id als URI und lehnt my_tool:output ab. Nutzen Sie my_tool_output oder $defs statt Doppelpunkte in $id. Unabhängig vom Unwrap, scheitert in derselben Pipeline.
Wie sieht structuredContent nach Unwrap aus?
Unter Protokoll 2025-11-25 erwarten 2.4.0-Clients Felder wie region und count an der structuredContent-Wurzel. Erstes Beispiel nach Upgrade, zweites legacy mit result.
{
"structuredContent": {
"region": "apac",
"count": 42
}
}
{
"structuredContent": {
"result": {
"region": "apac",
"count": 42
}
}
}
Nicht jede Antwort ist unwrapped. Ist nur result der Schlüssel, Schema auf das innere Objekt und legacy_wrapper loggen, um nicht neu gestartete Server zu zählen.
Wie beschreibt der Audit-Datensatz diesen tools/call?
Das Gate braucht protocol, Toolname und ob Output mit z.object().meta({ id }) deklariert wurde. Beispiel: metaIdTool true, protocol 2025-11-25. Fehlendes tool oder falsches protocol liefert zuerst schema, nicht validate_root erzwingen.
{
"tool": "get_report",
"protocol": "2025-11-25",
"metaIdTool": true
}
Form des Audit-Datensatzes per Schema unten fixieren. Normale Tools mit metaIdTool false nutzen validate_root; fehlen Felder, liefert missing, nicht legacy_wrapper.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["tool", "protocol", "metaIdTool"],
"properties": {
"tool": { "type": "string", "minLength": 1 },
"protocol": { "enum": ["2025-11-25", "2026-07-28"] },
"metaIdTool": { "type": "boolean" }
}
}
Warum scheitert Ajv sofort am $id des Output-Schemas?
2.4.0 hebt Ajv für fast-uri 3.1.8 an. fast-uri parst $id als URI; der Doppelpunkt in my_tool:output wird zum Schema und ist ungültig. my_tool_output oder $defs mit $ref nutzen. Unabhängig von der structuredContent-Schicht, trifft oft dieselbe CI wie Unwrap.
{
"$id": "my_tool:output",
"type": "object",
"additionalProperties": false,
"required": ["region"],
"properties": {
"region": { "type": "string" }
}
}
| Antwort und Datensatz | Gate liefert | Schicht für Schema |
|---|---|---|
| metaIdTool, protocol 2025-11-25, region an der Wurzel | validate_root | Output-Schema auf structuredContent-Wurzel. |
| Gleicher Datensatz, structuredContent nur result | legacy_wrapper | Output-Schema auf Objekt in structuredContent.result. |
| Audit ohne tool oder protocol nicht in enum | schema | Zuerst Audit-JSON reparieren, Pfad nicht raten. |
| Datensatz ok, body ohne structuredContent | missing | Als unvollständige Antwort behandeln, nicht still passieren. |
Wie wählt structured_content_target zwischen vier Zweigen?
Reihenfolge: record per Audit-Schema; body.structuredContent lesen. Bei metaIdTool und 2025-11-25 nur Schlüssel result → legacy_wrapper, sonst validate_root. metaIdTool false immer validate_root. Kein Objekt → missing.
legacy_wrapper und validate_root nicht zu einem Boolean passed zusammenlegen. Getrennt zählen, um result-Hüllen nach Upgrade zu sehen. Legacy-Regeln auf plain Tools erzeugen Fehl-Negative.
def actual_type(data: object) -> str:
if data is None:
return "null"
if isinstance(data, bool):
return "boolean"
if isinstance(data, list):
return "array"
if isinstance(data, int):
return "integer"
if isinstance(data, float):
return "number"
if isinstance(data, str):
return "string"
if isinstance(data, dict):
return "object"
raise TypeError(type(data))
def validate(data: object, schema: dict) -> list[str]:
errors: list[str] = []
if not isinstance(schema, dict):
return errors
types = schema.get("type")
if isinstance(types, str):
types = [types]
actual = actual_type(data)
if types and not any(t == actual or (t == "number" and actual == "integer") for t in types):
errors.append("type")
if "enum" in schema and not any(value == data for value in schema["enum"]):
errors.append("enum")
if isinstance(data, str) and "minLength" in schema and len(data) < schema["minLength"]:
errors.append("minLength")
if isinstance(data, dict) and schema.get("properties"):
for key in schema.get("required") or []:
if key not in data:
errors.append(f"required:{key}")
if schema.get("additionalProperties") is False:
for key in data:
if key not in schema["properties"]:
errors.append(f"additional:{key}")
for key, sub in schema["properties"].items():
if key in data and data[key] is not None:
errors.extend(validate(data[key], sub))
return errors
def structured_content_target(record: dict, body: dict, schema: dict) -> str:
if validate(record, schema):
return "schema"
content = body.get("structuredContent")
if not isinstance(content, dict):
return "missing"
if record.get("metaIdTool") and record.get("protocol") == "2025-11-25":
if set(content.keys()) == {"result"} and isinstance(content.get("result"), dict):
return "legacy_wrapper"
return "validate_root"
return "validate_root"
Wie vergleicht man Wurzel und result in JSONNote?
- 1Audit-Datensatz per Schema fixieren
Öffnen Sie JSON Schema. Audit-Schema ins erste Feld, record aus tools/call ins zweite. Falsches protocol oder metaIdTool zuerst markieren; bei schlechter Form keinen Pfad wählen.
- 2Format zur structuredContent-Schicht
Öffnen Sie JSON formatieren, gesamtes tools/call-Ergebnis einfügen. Prüfen, ob structuredContent flache Wurzel oder nur result ist.
- 3Diff: unwrapped vs wrapped
Öffnen Sie JSON Diff. Links 2.4.0-Wurzel, rechts legacy result. Der hervorgehobene Pfad ist die Zeile im Audit-Skript.
Häufige Fragen
Unwrappt Protokoll 2026-07-28 structuredContent auch?
Für die meta-id-result-Hülle hängt Unwrap in 2.4.0 an 2025-11-25; 2026-07-28 änderte diese Schicht nicht erneut. SDK-Notes lesen beim Revisionswechsel, aber 2026-07-28 unwrappt nicht automatisch noch einmal.
Entfernen 2026-07-28-Clients x-mcp-header bei number tools aus der Liste—Relevanz für Validierung?
Ändert nicht, welche Schicht structuredContent-Schema bekommt. Defensive tools/list-Änderung: number tools exposieren x-mcp-header nicht mehr. Audit schema weiter tools/call-Body, Unwrap nicht aus list-Annotation ableiten.
metaIdTool false trotzdem legacy_wrapper?
Nein. legacy_wrapper nur für Tools mit meta-id-Output unter 2025-11-25. Plain tools immer validate_root; fehlt structuredContent oder kein Objekt → missing.
Immer structuredContent.result validieren für Kompatibilität?
Nein. Unter 2025-11-25 liegen Felder an der Wurzel; nur result prüfen scheitert gültige Antworten und übersieht fehlendes result. Gate-Zweige oder content.result nur bei legacy_wrapper.
Ajv meldet ungültige $id URI—Antwort oder Schema fixen?
Output-Schema-Datei fixen, nicht die tools/call-Antwort. Doppelpunkte in $id durch Unterstriche oder $defs-Referenzen ersetzen. Unwrap und $id-Parsing sind getrennt, scheitern aber oft in einem CI-Job.
Fazit
2.4.0 liefert meta-id-Tools unter 2025-11-25 flach an der Wurzel, legacy-Server können result weiter liefern. Audits müssen verzweigen, nicht .result fest codieren.
Entscheidung: protocol 2025-11-25 und metaIdTool true, Felder an der Wurzel → validate_root; nur result → legacy_wrapper und Innenobjekt prüfen; Audit scheitert → schema; kein structuredContent → missing; keine Doppelpunkte in Output-$id.
Als Nächstes: Audit per Schema, Format für die structuredContent-Schicht, Diff unwrapped vs wrapped, Output-$id korrigieren.