From e15d722db9f79fbb177cce009260d9b715fddf04 Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Fri, 12 Jun 2026 08:50:49 +0000 Subject: [PATCH] Add multi-version protocol types: per-version delta packages One superset type set in mcp.types plus per-version packages that define only what changed against the previous version and re-export the rest; serialization validates dumps through the negotiated version's models. --- docs/migration.md | 19 +- pyproject.toml | 10 + scripts/update_spec_types.py | 749 ++++ src/mcp/shared/version.py | 10 +- src/mcp/types/__init__.py | 88 + src/mcp/types/_spec_names.py | 159 + src/mcp/types/_types.py | 1144 +++++- src/mcp/types/_versions.py | 164 + src/mcp/types/_wire_base.py | 35 + src/mcp/types/jsonrpc.py | 141 +- src/mcp/types/v2024_11_05/__init__.py | 1452 ++++++++ src/mcp/types/v2025_03_26/__init__.py | 479 +++ src/mcp/types/v2025_06_18/__init__.py | 1205 +++++++ src/mcp/types/v2025_11_25/__init__.py | 2504 +++++++++++++ src/mcp/types/v2026_07_28/__init__.py | 2482 +++++++++++++ src/mcp/types/wire.py | 733 ++++ tests/spec_oracles/PINNED.json | 54 + tests/spec_oracles/__init__.py | 0 tests/spec_oracles/_base.py | 14 + tests/spec_oracles/_harness.py | 747 ++++ tests/spec_oracles/burndown_allowlist.json | 2344 ++++++++++++ tests/spec_oracles/ext_tasks.py | 695 ++++ tests/spec_oracles/test_burndown.py | 136 + tests/spec_oracles/test_harness_internals.py | 277 ++ tests/spec_oracles/test_oracles_smoke.py | 93 + tests/spec_oracles/v2024_11_05.py | 1590 +++++++++ tests/spec_oracles/v2025_03_26.py | 1705 +++++++++ tests/spec_oracles/v2025_06_18.py | 2065 +++++++++++ tests/spec_oracles/v2025_11_25.py | 3256 +++++++++++++++++ tests/spec_oracles/v2026_07_28.py | 3346 ++++++++++++++++++ tests/types/__init__.py | 0 tests/types/test_import_budget.py | 36 + tests/types/test_public_surface.py | 312 ++ tests/types/test_spec_names.py | 84 + tests/types/test_version_registry.py | 165 + tests/types/test_version_surfaces.py | 693 ++++ tests/types/test_wire_boundary.py | 850 +++++ tests/types/test_wire_parse.py | 544 +++ 38 files changed, 30289 insertions(+), 91 deletions(-) create mode 100644 scripts/update_spec_types.py create mode 100644 src/mcp/types/_spec_names.py create mode 100644 src/mcp/types/_versions.py create mode 100644 src/mcp/types/_wire_base.py create mode 100644 src/mcp/types/v2024_11_05/__init__.py create mode 100644 src/mcp/types/v2025_03_26/__init__.py create mode 100644 src/mcp/types/v2025_06_18/__init__.py create mode 100644 src/mcp/types/v2025_11_25/__init__.py create mode 100644 src/mcp/types/v2026_07_28/__init__.py create mode 100644 src/mcp/types/wire.py create mode 100644 tests/spec_oracles/PINNED.json create mode 100644 tests/spec_oracles/__init__.py create mode 100644 tests/spec_oracles/_base.py create mode 100644 tests/spec_oracles/_harness.py create mode 100644 tests/spec_oracles/burndown_allowlist.json create mode 100644 tests/spec_oracles/ext_tasks.py create mode 100644 tests/spec_oracles/test_burndown.py create mode 100644 tests/spec_oracles/test_harness_internals.py create mode 100644 tests/spec_oracles/test_oracles_smoke.py create mode 100644 tests/spec_oracles/v2024_11_05.py create mode 100644 tests/spec_oracles/v2025_03_26.py create mode 100644 tests/spec_oracles/v2025_06_18.py create mode 100644 tests/spec_oracles/v2025_11_25.py create mode 100644 tests/spec_oracles/v2026_07_28.py create mode 100644 tests/types/__init__.py create mode 100644 tests/types/test_import_budget.py create mode 100644 tests/types/test_public_surface.py create mode 100644 tests/types/test_spec_names.py create mode 100644 tests/types/test_version_registry.py create mode 100644 tests/types/test_version_surfaces.py create mode 100644 tests/types/test_wire_boundary.py create mode 100644 tests/types/test_wire_parse.py diff --git a/docs/migration.md b/docs/migration.md index 850e052550..b7444400b6 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -1166,11 +1166,18 @@ In practice, replace direct `ServerSession` use with `Server.run(read_stream, wr `BaseSession` is still used by `ClientSession`, which never relied on these members. `RequestResponder.respond()` is unchanged. -### Experimental Tasks support removed +### Experimental Tasks support removed (types restored, types-only) -Tasks (SEP-1686) have been removed from the MCP specification and are no longer part of this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules have been removed, along with all `Task*` types, the `tasks` capability fields, `Tool.execution`, and the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. +Tasks (SEP-1686) runtime support has been removed from this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules are gone, along with the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. -Tasks are expected to return as a separate MCP extension in a future release. +The 2025-11-25 protocol *types* are back in `mcp.types` so that 2025-11-25 task payloads can still be modeled: the `Task*` types, the `tasks` capability subtrees, `Tool.execution`, and the `task` field on the four task-augmentable params classes. They are types-only definitions: + +- Attributes are snake_case (`task_id`, `created_at`), aliased to the camelCase wire names. +- Timestamps are plain `str` values, not `datetime`. +- `GetTaskPayloadResult` follows the default extra-field policy (`ignore`), so it retains only `_meta`; validate a tasks/result payload into the original request's result type instead. +- None of the task methods is a member of the request/notification unions, and `add_request_handler` does not dispatch them. + +Tasks runtime support is expected to return as a separate MCP extension in a future release. ## Deprecations @@ -1214,6 +1221,12 @@ If you relied on extra fields round-tripping through MCP types, move that data i ## New Features +### Newer protocol fields are modeled and retained + +`mcp.types` now models the 2025-11-25 and 2026-07-28 protocol fields and types (for example `resultType`, `ttlMs`/`cacheScope` on the cacheable results, and `inputResponses`/`requestState` on retried requests). Inbound payloads carrying these keys used to lose them to the unknown-field policy on re-dump; they now parse into typed fields and survive a user-level round-trip. All of the new fields are optional with `None` defaults, so dumps of values that do not set them are unchanged. + +`CallToolResult.structured_content` and `ToolResultContent.structured_content` now accept any JSON value, where previous releases required an object: 2026-07-28 allows scalar and array structured content. Parsing is strictly more lenient, and emitted bytes for object values are unchanged. + ### `streamable_http_app()` available on lowlevel Server The `streamable_http_app()` method is now available directly on the lowlevel `Server` class, not just `MCPServer`. This allows using the streamable HTTP transport without the MCPServer wrapper. diff --git a/pyproject.toml b/pyproject.toml index 749af47ab6..8941d5b2a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,6 +143,12 @@ venv = ".venv" # those private functions instead of testing the private functions directly. It makes it easier to maintain the code source # and refactor code that is not public. executionEnvironments = [ + # tests/spec_oracles: generated spec-oracle modules ported from the codegen + # pipeline and committed unchanged; the generator's recursive JSON alias and + # envelope field-override patterns trip three strict checks. + { root = "tests/spec_oracles", extraPaths = [ + ".", + ], reportUnusedFunction = false, reportPrivateUsage = false, reportGeneralTypeIssues = false, reportUnknownVariableType = false, reportInvalidTypeForm = false }, { root = "tests", extraPaths = [ ".", ], reportUnusedFunction = false, reportPrivateUsage = false }, @@ -177,6 +183,10 @@ max-complexity = 24 # Default is 10 [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] +# Generated spec-oracle modules: docstrings come verbatim from the spec schema +# and ruff format does not wrap long docstring lines. +"tests/spec_oracles/v*.py" = ["E501"] +"tests/spec_oracles/ext_tasks.py" = ["E501"] "tests/server/mcpserver/test_func_metadata.py" = ["E501"] "tests/shared/test_progress_notifications.py" = ["PLW0603"] diff --git a/scripts/update_spec_types.py b/scripts/update_spec_types.py new file mode 100644 index 0000000000..705d9fc517 --- /dev/null +++ b/scripts/update_spec_types.py @@ -0,0 +1,749 @@ +"""Regenerate the per-spec-version oracle modules under tests/spec_oracles/. + +Pipeline, per selected entry of tests/spec_oracles/PINNED.json: + +1. Fetch ``schema.json`` (and ``schema.ts`` for the protocol-version string) at + the pinned commit SHA, via ``gh api`` — or read both from ``--schema-dir``. +2. Generate pydantic models with the pinned datamodel-code-generator version, + invoked through ``uvx`` (no project dependency). +3. Post-process with stdlib ast/re: ``RootModel`` wrappers become type aliases + (recursive groups become ``TypeAliasType``), the synthetic ``Model`` root + artifact and stale ``model_rebuild()`` calls are dropped, ``{@link X}`` + JSDoc artifacts are unwrapped, and the per-version rename map is applied. +4. Prepend a provenance header and append the ``SPEC_DEFS`` manifest. +5. ``ruff check --fix`` + ``ruff format`` on a staging file inside + tests/spec_oracles/ (so the repo config and first-party import + classification apply); any residual finding other than E501 (per-file- + ignored for generated modules) aborts the run. +6. Verify: import the staged candidate, audit that every schema definition + resolves to a module attribute and that field wire names and requiredness + match the schema. Only then rename into ``tests/spec_oracles/.py`` + and rewrite the derived PINNED.json fields. + +Usage (from the repo root):: + + uv run --frozen python scripts/update_spec_types.py [KEY ...] [options] + + KEY entries to (re)generate: 2024-11-05, 2025-03-26, 2025-06-18, + 2025-11-25, draft, tasks. Default: all non-frozen entries. + --all select every entry. + --sha SHA override the pinned SHA for the (single) selected entry and + write it back to PINNED.json on success. + --schema-dir D offline mode: read schemas from D instead of fetching. + Naming: schema-.json / schema-.ts for core + entries, ext-tasks-schema.json for tasks. The caller asserts + the files correspond to the pinned SHAs. + --check regenerate and diff against the committed modules; exit 1 on + drift, change nothing. + --force allow regenerating frozen (released-version) entries. +""" + +from __future__ import annotations + +import argparse +import ast +import importlib.util +import json +import re +import subprocess +import sys +import tempfile +from dataclasses import dataclass +from pathlib import Path +from types import ModuleType +from typing import Any + +from pydantic import BaseModel + +REPO_ROOT = Path(__file__).resolve().parent.parent +ORACLES_DIR = REPO_ROOT / "tests" / "spec_oracles" +PINNED_PATH = ORACLES_DIR / "PINNED.json" +ALLOWLIST_PATH = ORACLES_DIR / "burndown_allowlist.json" + +# Escape hatch for generator name mangling that breaks the def-name audit +# (PINNED key -> {schema def name -> module attribute name}). Entries are added +# only when that audit fails, with a comment citing the colliding def. Matching +# the hand-written SDK's deliberate renames is the burn-down harness's job, not +# the oracle's — oracles stay schema-faithful. +RENAME_MAP: dict[str, dict[str, str]] = { + "2024-11-05": {}, + "2025-03-26": {}, + "2025-06-18": {}, + "2025-11-25": {}, + "draft": {}, + "tasks": {}, +} + +_PROTOCOL_VERSION_RE = re.compile(r'LATEST_PROTOCOL_VERSION\s*=\s*"([^"]+)"') +_JSDOC_LINK_RE = re.compile(r"\{@link\s+([^}]+?)\s*\}") +_GENERATOR_BANNER_RE = re.compile(r"\A# generated by datamodel-codegen:\n(# filename: [^\n]*\n)?\n*") + +HEADER_TEMPLATE = """\ +# GENERATED FILE — DO NOT EDIT. +# Source: https://github.com/{repo}/blob/{sha}/{schema_path} +# Protocol version: {protocol_version} Generator: datamodel-code-generator {generator_version} +# Regenerate: uv run --frozen python scripts/update_spec_types.py {key} [--sha ] +# pyright: reportIncompatibleVariableOverride=false +""" + + +class PipelineError(Exception): + """A stage of the pipeline failed; the message says which and why.""" + + +@dataclass +class Entry: + """One PINNED.json entry (a core spec version or an extension).""" + + key: str + repo: str + sha: str + schema_path: str + protocol_version: str | None + module: str + frozen: bool + is_extension: bool + + +@dataclass +class Outcome: + """What happened for one regenerated entry (for the final summary).""" + + entry: Entry + def_count: int + converted_aliases: int + recursive_aliases: list[str] + generator_warnings: list[str] + e501_count: int + action: str = "" + + +@dataclass +class PostprocessResult: + """Post-processed source plus counters for the summary.""" + + source: str + converted: int + recursive: list[str] + + +def load_pinned() -> dict[str, Any]: + """Read PINNED.json.""" + with PINNED_PATH.open() as f: + return json.load(f) + + +def iter_entries(pinned: dict[str, Any]) -> list[Entry]: + """Flatten PINNED.json into Entry records (core versions, then extensions).""" + entries: list[Entry] = [] + for key, data in pinned["versions"].items(): + entries.append( + Entry( + key=key, + repo=pinned["spec_repo"], + sha=data["sha"], + schema_path=data["schema_path"], + protocol_version=data["protocol_version"], + module=data["module"], + frozen=data["frozen"], + is_extension=False, + ) + ) + for key, data in pinned["extensions"].items(): + entries.append( + Entry( + key=key, + repo=data["repo"], + sha=data["sha"], + schema_path=data["schema_path"], + protocol_version=data["protocol_version"], + module=data["module"], + frozen=data["frozen"], + is_extension=True, + ) + ) + return entries + + +def gh_fetch(repo: str, path: str, sha: str) -> str: + """Fetch one file from GitHub at a pinned SHA via `gh api`.""" + result = subprocess.run( + [ + "gh", + "api", + "-H", + "Accept: application/vnd.github.raw+json", + f"repos/{repo}/contents/{path}?ref={sha}", + ], + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise PipelineError(f"gh api fetch of {repo}:{path}@{sha} failed: {result.stderr.strip()}") + return result.stdout + + +def obtain_schemas(entry: Entry, sha: str, schema_dir: Path | None) -> tuple[str, str | None]: + """Return (schema.json text, schema.ts text or None) for an entry.""" + if schema_dir is not None: + if entry.is_extension: + json_path, ts_path = schema_dir / "ext-tasks-schema.json", None + else: + json_path = schema_dir / f"schema-{entry.key}.json" + ts_path = schema_dir / f"schema-{entry.key}.ts" + if not json_path.is_file(): + raise PipelineError(f"{entry.key}: --schema-dir is missing {json_path.name}") + ts_text: str | None = None + if ts_path is not None: + if not ts_path.is_file(): + raise PipelineError(f"{entry.key}: --schema-dir is missing {ts_path.name}") + ts_text = ts_path.read_text() + return json_path.read_text(), ts_text + schema_json = gh_fetch(entry.repo, entry.schema_path, sha) + ts_text = None + if not entry.is_extension: + ts_remote_path = entry.schema_path.removesuffix("schema.json") + "schema.ts" + ts_text = gh_fetch(entry.repo, ts_remote_path, sha) + return schema_json, ts_text + + +def extract_protocol_version(entry: Entry, ts_text: str | None) -> str | None: + """Extract LATEST_PROTOCOL_VERSION from schema.ts; None for extensions.""" + if entry.is_extension: + return None + if ts_text is None: + raise PipelineError(f"{entry.key}: schema.ts is required to extract the protocol version") + match = _PROTOCOL_VERSION_RE.search(ts_text) + if match is None: + raise PipelineError(f"{entry.key}: LATEST_PROTOCOL_VERSION not found in schema.ts") + version = match.group(1) + if entry.key != "draft" and version != entry.key: + raise PipelineError( + f"{entry.key}: released schema.ts declares LATEST_PROTOCOL_VERSION={version!r}; " + "released schemas are immutable, so this indicates a wrong SHA or schema file" + ) + return version + + +def module_name_for(entry: Entry, protocol_version: str | None) -> str: + """Derive the oracle module name from the protocol version string.""" + if entry.is_extension: + return f"ext_{entry.key}" + if protocol_version is None: + raise PipelineError(f"{entry.key}: cannot derive a module name without a protocol version") + return "v" + re.sub(r"[^a-z0-9]+", "_", protocol_version.lower()) + + +def run_generator(schema_json_path: Path, output_path: Path, generator_version: str) -> list[str]: + """Run the pinned datamodel-code-generator via uvx; return its warning lines.""" + result = subprocess.run( + [ + "uvx", + "--from", + f"datamodel-code-generator=={generator_version}", + "datamodel-codegen", + "--input", + str(schema_json_path), + "--input-file-type", + "jsonschema", + "--output", + str(output_path), + "--output-model-type", + "pydantic_v2.BaseModel", + "--target-python-version", + "3.10", + "--base-class", + "tests.spec_oracles._base.OracleModel", + "--snake-case-field", + "--remove-special-field-name-prefix", + "--use-annotated", + "--use-field-description", + "--use-schema-description", + "--enum-field-as-literal", + "all", + "--use-union-operator", + "--use-double-quotes", + "--extra-fields", + "allow", + "--disable-timestamp", + ], + capture_output=True, + text=True, + ) + if result.returncode != 0 or not output_path.is_file(): + raise PipelineError(f"datamodel-codegen failed:\n{result.stderr.strip()}") + return [line for line in result.stderr.splitlines() if "Warning" in line and "FutureWarning" not in line] + + +def _collect_rootmodel_aliases(tree: ast.Module) -> dict[str, tuple[ast.ClassDef, str]]: + """Map class name -> (node, unparsed RootModel type argument) for RootModel subclasses.""" + aliases: dict[str, tuple[ast.ClassDef, str]] = {} + for node in tree.body: + if not isinstance(node, ast.ClassDef) or len(node.bases) != 1: + continue + base = node.bases[0] + if isinstance(base, ast.Subscript) and isinstance(base.value, ast.Name) and base.value.id == "RootModel": + aliases[node.name] = (node, ast.unparse(base.slice)) + return aliases + + +def _find_recursive_aliases(aliases: dict[str, tuple[ast.ClassDef, str]]) -> set[str]: + """Names of alias classes that participate in a reference cycle.""" + + def refs(type_expr: str) -> set[str]: + bare = type_expr.replace("'", " ").replace('"', " ") + return {name for name in aliases if re.search(rf"\b{name}\b", bare)} + + cyclic: set[str] = set() + for name, (_, type_expr) in aliases.items(): + seen: set[str] = set() + stack = list(refs(type_expr)) + while stack: + current = stack.pop() + if current == name: + cyclic.add(name) + break + if current in seen or current not in aliases: + continue + seen.add(current) + stack.extend(refs(aliases[current][1])) + return cyclic + + +def _alias_statement(name: str, node: ast.ClassDef, alias_type: str, *, string_form: bool) -> str: + """One converted alias definition (docstring preserved as a trailing string).""" + if string_form: + value = alias_type.replace("'", "").replace('"', "") + text = f'{name} = TypeAliasType("{name}", "{value}")\n' + else: + text = f'{name} = TypeAliasType("{name}", {alias_type})\n' + docstring = ast.get_docstring(node) + if docstring and '"""' not in docstring: + text += '"""' + docstring + '"""\n' + return text + + +def _emit_cyclic_block(ordered: list[str], aliases: dict[str, tuple[ast.ClassDef, str]]) -> str: + """Emit a group of mutually recursive aliases in the one form pyright accepts. + + Empirical pyright (1.1.405) behavior for call-form TypeAliasType groups: + quoted self-references in an expression-form value error with + reportInvalidTypeForm whenever the value also references another cyclic + alias, quoted forward references from expression-form values error + unconditionally, and any emission errors if a usage site precedes the + definitions. The working emission is: members whose value references + itself go first as a whole-string value (lazily parsed), the remaining + members follow in expression form so their quoted references point + backwards — and the whole block must be placed before its first use + (the caller relocates it to just after the imports). + """ + + def has_self_ref(name: str) -> bool: + bare = aliases[name][1].replace("'", " ").replace('"', " ") + return re.search(rf"\b{name}\b", bare) is not None + + string_members = [n for n in ordered if has_self_ref(n)] + expr_members = [n for n in ordered if not has_self_ref(n)] + parts = [ + _alias_statement(name, aliases[name][0], aliases[name][1], string_form=name in string_members) + for name in [*string_members, *expr_members] + ] + return "\n\n".join(parts) + + +# Names a relocated cyclic-alias block may reference besides its own members: +# anything else means relocation could break runtime evaluation order. +_RELOCATABLE_NAMES = frozenset( + {"Annotated", "Any", "Literal", "None", "Optional", "Union"} + | {"bool", "bytes", "dict", "float", "frozenset", "int", "list", "set", "str", "tuple"} +) + + +def _group_is_self_contained(cyclic: set[str], aliases: dict[str, tuple[ast.ClassDef, str]]) -> bool: + """True if the cyclic group references only its own members and builtins.""" + allowed = cyclic | _RELOCATABLE_NAMES + for name in cyclic: + bare = aliases[name][1].replace("'", " ").replace('"', " ") + identifiers = set(re.findall(r"[A-Za-z_][A-Za-z0-9_]*", bare)) + if not identifiers <= allowed: + return False + return True + + +def _end_lineno(node: ast.stmt) -> int: + """End line of a statement (ast fills end_lineno in for parsed code).""" + end = node.end_lineno + if end is None: + raise PipelineError(f"ast node at line {node.lineno} has no end_lineno") + return end + + +def postprocess(source: str, rename_map: dict[str, str]) -> PostprocessResult: + """Convert RootModel wrappers to aliases and clean up generator artifacts.""" + source = _GENERATOR_BANNER_RE.sub("", source) + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + + aliases = _collect_rootmodel_aliases(tree) + cyclic = _find_recursive_aliases(aliases) + + converted = 0 + cyclic_in_order = [name for name in aliases if name in cyclic] + relocate = bool(cyclic) and _group_is_self_contained(cyclic, aliases) + replacements: list[tuple[int, int, str]] = [] # 1-based inclusive line ranges + for name, (node, alias_type) in aliases.items(): + if name == "Model" and alias_type == "Any": + # The generator's synthetic artifact for the schema's empty root. + replacements.append((node.lineno, _end_lineno(node), "")) + continue + if name in cyclic: + # The whole group is emitted as one block (see _emit_cyclic_block): + # relocated to just after the imports when self-contained, else at + # the first member's position (pyright will flag usage-before- + # definition there; that is an escalation, not a workaround site). + if relocate or name != cyclic_in_order[0]: + new = "" + else: + new = _emit_cyclic_block(cyclic_in_order, aliases) + else: + new = f"{name}: TypeAlias = {alias_type}\n" + docstring = ast.get_docstring(node) + if docstring and '"""' not in docstring: + new += '"""' + docstring + '"""\n' + replacements.append((node.lineno, _end_lineno(node), new)) + converted += 1 + if relocate: + last_import_end = max(_end_lineno(node) for node in tree.body if isinstance(node, ast.Import | ast.ImportFrom)) + block = _emit_cyclic_block(cyclic_in_order, aliases) + replacements.append((last_import_end, last_import_end, lines[last_import_end - 1] + "\n" + block)) + + # Import fixes, on the same parsed tree: drop RootModel from the pydantic + # import, add TypeAlias / TypeAliasType imports where the conversions need + # them. ruff's import sorting normalizes placement afterwards. + need_type_alias = converted > len(cyclic) + need_type_alias_type = bool(cyclic) + typing_handled = False + for node in tree.body: + if not isinstance(node, ast.ImportFrom): + continue + if node.module == "pydantic": + names = [alias for alias in node.names if alias.name != "RootModel"] + text = ast.unparse(ast.ImportFrom(module="pydantic", names=names, level=0)) + "\n" if names else "" + replacements.append((node.lineno, _end_lineno(node), text)) + elif node.module == "typing": + typing_handled = True + names = list(node.names) + if need_type_alias and not any(alias.name == "TypeAlias" for alias in names): + names.append(ast.alias(name="TypeAlias")) + text = ast.unparse(ast.ImportFrom(module="typing", names=names, level=0)) + "\n" + if need_type_alias_type: + text += "from typing_extensions import TypeAliasType\n" + replacements.append((node.lineno, _end_lineno(node), text)) + if not typing_handled and (need_type_alias or need_type_alias_type): + extra = "from typing import TypeAlias\n" if need_type_alias else "" + if need_type_alias_type: + extra += "from typing_extensions import TypeAliasType\n" + for node in tree.body: + if isinstance(node, ast.ImportFrom) and node.module == "__future__": + replacements.append((node.lineno, _end_lineno(node), lines[node.lineno - 1] + extra)) + break + + for start, end, new in sorted(replacements, reverse=True): + lines[start - 1 : end] = [new] + source = "".join(lines) + + # model_rebuild() is only valid on classes; drop calls for converted names. + for name in [*aliases, "Model"]: + source = re.sub(rf"^{name}\.model_rebuild\(\)\n", "", source, flags=re.MULTILINE) + + source = _JSDOC_LINK_RE.sub(r"\1", source) + + for old, new_name in rename_map.items(): + source = re.sub(rf"\b{old}\b", new_name, source) + + return PostprocessResult(source=source, converted=converted, recursive=sorted(cyclic)) + + +def schema_defs(schema: dict[str, Any]) -> dict[str, Any]: + """The definitions table of a schema ($defs in 2020-12, definitions in draft-07).""" + defs = schema.get("$defs") or schema.get("definitions") + if not defs: + raise PipelineError("schema has no $defs/definitions table") + return defs + + +def build_manifest(schema: dict[str, Any], rename_map: dict[str, str]) -> str: + """Render the SPEC_DEFS manifest appended to each generated module.""" + names = sorted(rename_map.get(name, name) for name in schema_defs(schema)) + body = "".join(f' "{name}",\n' for name in names) + return f"\nSPEC_DEFS: tuple[str, ...] = (\n{body})\n" + + +def run_ruff(path: Path) -> int: + """ruff check --fix + format with the repo config; return the residual E501 count. + + Any residual finding other than E501 (which is per-file-ignored for the + generated modules once installed) aborts the pipeline. + """ + common = ["uv", "run", "--frozen", "ruff"] + fix = subprocess.run( + [*common, "check", "--no-cache", "--fix", "--exit-zero", str(path)], + cwd=REPO_ROOT, + capture_output=True, + text=True, + ) + if fix.returncode != 0: + raise PipelineError(f"ruff check --fix failed:\n{fix.stderr.strip()}") + report = subprocess.run( + [*common, "check", "--no-cache", "--output-format", "json", "--exit-zero", str(path)], + cwd=REPO_ROOT, + capture_output=True, + text=True, + ) + if report.returncode != 0: + raise PipelineError(f"ruff check (report) failed:\n{report.stderr.strip()}") + findings: list[dict[str, Any]] = json.loads(report.stdout) + residual = [f for f in findings if f.get("code") != "E501"] + if residual: + details = "\n".join( + f" {f.get('code')}: {f.get('message')} (line {f.get('location', {}).get('row')})" for f in residual + ) + raise PipelineError(f"ruff residual findings other than E501 in {path.name}:\n{details}") + fmt = subprocess.run([*common, "format", "--no-cache", str(path)], cwd=REPO_ROOT, capture_output=True, text=True) + if fmt.returncode != 0: + raise PipelineError(f"ruff format failed:\n{fmt.stderr.strip()}") + return len(findings) - len(residual) + + +def load_schema_gap_exemptions(module: str) -> set[tuple[str, str | None]]: + """(def name, field-or-None) audit exemptions: allowlist entries categorized schema-gap.""" + if not ALLOWLIST_PATH.is_file(): + return set() + with ALLOWLIST_PATH.open() as f: + allowlist: dict[str, Any] = json.load(f) + return { + (entry["name"], entry.get("field")) + for entry in allowlist.get("entries", []) + if entry.get("category") == "schema-gap" and entry.get("oracle") == module + } + + +def import_candidate(path: Path, module: str) -> ModuleType: + """Import a staged candidate module under a private name.""" + name = f"_oracle_candidate_{module}" + spec = importlib.util.spec_from_file_location(name, path) + if spec is None or spec.loader is None: + raise PipelineError(f"cannot load candidate module from {path}") + mod = importlib.util.module_from_spec(spec) + sys.modules[name] = mod + spec.loader.exec_module(mod) + return mod + + +def audit_candidate(mod: ModuleType, schema: dict[str, Any], entry: Entry, rename_map: dict[str, str]) -> None: + """Def-name + alias + requiredness audit; raises on any failure.""" + problems: list[str] = [] + exempt = load_schema_gap_exemptions(entry.module) + for def_name, def_schema in schema_defs(schema).items(): + attr = rename_map.get(def_name, def_name) + obj = getattr(mod, attr, None) + if obj is None: + problems.append(f"def {def_name!r} does not resolve to attribute {attr!r}") + continue + if not (isinstance(obj, type) and issubclass(obj, BaseModel)): + continue # type aliases / Literal aliases have no fields to audit + if (attr, None) in exempt: + continue + properties: dict[str, Any] = def_schema.get("properties", {}) + required: list[str] = def_schema.get("required", []) + fields_by_wire_name = { + (info.serialization_alias or info.alias or field_name): info + for field_name, info in obj.model_fields.items() + } + for prop_name in properties: + if (attr, prop_name) in exempt: + continue + info = fields_by_wire_name.get(prop_name) + if info is None: + problems.append(f"{attr}: no field with wire name {prop_name!r}") + continue + if info.is_required() != (prop_name in required): + problems.append( + f"{attr}.{prop_name}: requiredness mismatch " + f"(model={info.is_required()}, schema={prop_name in required})" + ) + if problems: + raise PipelineError(f"{entry.key}: audit failed:\n" + "\n".join(f" - {p}" for p in problems)) + + +def import_check(module: str) -> None: + """The official post-install import gate, in a clean subprocess from the repo root.""" + result = subprocess.run( + ["uv", "run", "--frozen", "python", "-c", f"import tests.spec_oracles.{module}"], + cwd=REPO_ROOT, + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise PipelineError(f"import check failed for tests.spec_oracles.{module}:\n{result.stderr.strip()}") + + +def build_candidate(entry: Entry, sha: str, schema_dir: Path | None, generator_version: str) -> tuple[str, Outcome]: + """Stages 1-6 (through the audit) for one entry; returns (candidate source, outcome).""" + schema_json_text, ts_text = obtain_schemas(entry, sha, schema_dir) + schema: dict[str, Any] = json.loads(schema_json_text) + protocol_version = extract_protocol_version(entry, ts_text) + entry.protocol_version = protocol_version + entry.module = module_name_for(entry, protocol_version) + entry.sha = sha + rename_map = RENAME_MAP[entry.key] + + with tempfile.TemporaryDirectory() as tmp: + tmp_dir = Path(tmp) + schema_file = tmp_dir / "schema.json" + schema_file.write_text(schema_json_text) + raw_file = tmp_dir / "raw.py" + warnings = run_generator(schema_file, raw_file, generator_version) + processed = postprocess(raw_file.read_text(), rename_map) + + header = HEADER_TEMPLATE.format( + repo=entry.repo, + sha=sha, + schema_path=entry.schema_path, + protocol_version=entry.protocol_version or "n/a", + generator_version=generator_version, + key=entry.key, + ) + # Staged inside the package so ruff applies the repo config with in-repo + # first-party import classification, and so the audit import resolves + # `tests.spec_oracles._base` the same way the installed module will. + staging = ORACLES_DIR / f"_staging_{entry.module}.py" + try: + staging.write_text(header + processed.source + build_manifest(schema, rename_map)) + e501_count = run_ruff(staging) + mod = import_candidate(staging, entry.module) + audit_candidate(mod, schema, entry, rename_map) + candidate = staging.read_text() + finally: + staging.unlink(missing_ok=True) + + outcome = Outcome( + entry=entry, + def_count=len(schema_defs(schema)), + converted_aliases=processed.converted, + recursive_aliases=processed.recursive, + generator_warnings=warnings, + e501_count=e501_count, + ) + return candidate, outcome + + +def write_back_pinned(pinned: dict[str, Any], entries: list[Entry]) -> None: + """Rewrite the derived fields (sha, protocol_version, module) into PINNED.json.""" + by_key = {e.key: e for e in entries} + for table in (pinned["versions"], pinned["extensions"]): + for key, data in table.items(): + if key in by_key: + entry = by_key[key] + data["sha"] = entry.sha + data["protocol_version"] = entry.protocol_version + data["module"] = entry.module + PINNED_PATH.write_text(json.dumps(pinned, indent=2) + "\n") + + +def delete_orphans(pinned: dict[str, Any]) -> list[str]: + """Delete generated modules that no PINNED.json entry produces any more.""" + known = {data["module"] for data in pinned["versions"].values()} + known |= {data["module"] for data in pinned["extensions"].values()} + deleted: list[str] = [] + for path in sorted(ORACLES_DIR.glob("*.py")): + if path.stem.startswith(("_", "test_")) or path.name == "__init__.py": + continue + if path.stem not in known: + path.unlink() + deleted.append(path.name) + return deleted + + +def summarize(outcomes: list[Outcome]) -> None: + """Print the per-entry generation summary.""" + for o in outcomes: + print( + f"{o.entry.key}: {o.action} -> tests/spec_oracles/{o.entry.module}.py " + f"(defs={o.def_count}, alias_conversions={o.converted_aliases}, " + f"recursive={o.recursive_aliases or 'none'}, residual_E501={o.e501_count})" + ) + for warning in o.generator_warnings: + print(f" generator warning: {warning}") + + +def main(argv: list[str] | None = None) -> int: + """CLI entry point.""" + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("keys", nargs="*", help="PINNED.json entries to regenerate (default: all non-frozen)") + parser.add_argument("--all", action="store_true", help="select every entry") + parser.add_argument("--sha", help="override the pinned SHA (single KEY only); written back on success") + parser.add_argument("--schema-dir", type=Path, help="read schemas from this directory instead of fetching") + parser.add_argument("--check", action="store_true", help="diff regenerated output against committed modules") + parser.add_argument("--force", action="store_true", help="allow regenerating frozen entries") + args = parser.parse_args(argv) + + if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) + + pinned = load_pinned() + entries = iter_entries(pinned) + valid_keys = [e.key for e in entries] + if args.all: + selected = entries + elif args.keys: + unknown = [k for k in args.keys if k not in valid_keys] + if unknown: + parser.error(f"unknown KEY(s) {unknown}; valid: {valid_keys}") + selected = [e for e in entries if e.key in args.keys] + else: + selected = [e for e in entries if not e.frozen] + if args.sha and len(selected) != 1: + parser.error("--sha requires exactly one KEY") + + generator_version: str = pinned["generator"]["version"] + outcomes: list[Outcome] = [] + drifted: list[str] = [] + regenerated: list[Entry] = [] + for entry in selected: + target = ORACLES_DIR / f"{entry.module}.py" + if entry.frozen and target.is_file() and not (args.force or args.check): + print(f"{entry.key}: frozen (released schemas are generate-once); use --force to regenerate") + continue + candidate, outcome = build_candidate(entry, args.sha or entry.sha, args.schema_dir, generator_version) + target = ORACLES_DIR / f"{entry.module}.py" # module may have been re-derived + if args.check: + if target.is_file() and target.read_text() == candidate: + outcome.action = "checked" + else: + outcome.action = "drifted" + drifted.append(entry.module) + else: + if target.is_file() and target.read_text() == candidate: + outcome.action = "unchanged" + else: + target.write_text(candidate) + outcome.action = "installed" + import_check(entry.module) + regenerated.append(entry) + outcomes.append(outcome) + + if regenerated and not args.check: + write_back_pinned(pinned, regenerated) + for name in delete_orphans(pinned): + print(f"deleted orphaned module: {name}") + + summarize(outcomes) + if drifted: + print(f"DRIFT: {drifted} differ from committed output", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/mcp/shared/version.py b/src/mcp/shared/version.py index 44154da365..2273898442 100644 --- a/src/mcp/shared/version.py +++ b/src/mcp/shared/version.py @@ -16,8 +16,16 @@ "2025-03-26", "2025-06-18", "2025-11-25", + "2026-07-28", ) -"""Every released protocol revision, oldest to newest.""" +"""Every protocol revision this SDK knows, oldest to newest. + +Knowing a revision (its types and wire shapes are modeled) is independent of +being able to negotiate it; see SUPPORTED_PROTOCOL_VERSIONS for the latter. +2026-07-28 is known but unreleased — its schema is published but still +subject to change — so it stays out of SUPPORTED_PROTOCOL_VERSIONS and ahead +of LATEST_PROTOCOL_VERSION until the specification releases it. +""" SUPPORTED_PROTOCOL_VERSIONS: list[str] = ["2024-11-05", "2025-03-26", "2025-06-18", LATEST_PROTOCOL_VERSION] """Protocol revisions this SDK can negotiate.""" diff --git a/src/mcp/types/__init__.py b/src/mcp/types/__init__.py index cb49ff29db..92122e545d 100644 --- a/src/mcp/types/__init__.py +++ b/src/mcp/types/__init__.py @@ -6,17 +6,25 @@ # Re-export everything from _types for backward compatibility from mcp.types._types import ( + CLIENT_CAPABILITIES_META_KEY, + CLIENT_INFO_META_KEY, DEFAULT_NEGOTIATED_VERSION, LATEST_PROTOCOL_VERSION, + LOG_LEVEL_META_KEY, + PROTOCOL_VERSION_META_KEY, Annotations, AudioContent, BaseMetadata, BlobResourceContents, + CacheableResult, CallToolRequest, CallToolRequestParams, CallToolResult, CancelledNotification, CancelledNotificationParams, + CancelTaskRequest, + CancelTaskRequestParams, + CancelTaskResult, ClientCapabilities, ClientNotification, ClientRequest, @@ -33,6 +41,9 @@ CreateMessageRequestParams, CreateMessageResult, CreateMessageResultWithTools, + CreateTaskResult, + DiscoverRequest, + DiscoverResult, ElicitationCapability, ElicitationRequiredErrorData, ElicitCompleteNotification, @@ -49,6 +60,12 @@ GetPromptRequest, GetPromptRequestParams, GetPromptResult, + GetTaskPayloadRequest, + GetTaskPayloadRequestParams, + GetTaskPayloadResult, + GetTaskRequest, + GetTaskRequestParams, + GetTaskResult, Icon, IconTheme, ImageContent, @@ -58,6 +75,12 @@ InitializeRequest, InitializeRequestParams, InitializeResult, + InputRequest, + InputRequests, + InputRequiredResult, + InputResponse, + InputResponseRequestParams, + InputResponses, ListPromptsRequest, ListPromptsResult, ListResourcesRequest, @@ -66,12 +89,15 @@ ListResourceTemplatesResult, ListRootsRequest, ListRootsResult, + ListTasksRequest, + ListTasksResult, ListToolsRequest, ListToolsResult, LoggingCapability, LoggingLevel, LoggingMessageNotification, LoggingMessageNotificationParams, + MissingRequiredClientCapabilityErrorData, ModelHint, ModelPreferences, Notification, @@ -92,6 +118,7 @@ ReadResourceRequest, ReadResourceRequestParams, ReadResourceResult, + RelatedTaskMetadata, Request, RequestParams, RequestParamsMeta, @@ -105,6 +132,7 @@ ResourceUpdatedNotification, ResourceUpdatedNotificationParams, Result, + ResultType, Role, Root, RootsCapability, @@ -124,17 +152,29 @@ StopReason, SubscribeRequest, SubscribeRequestParams, + SubscriptionFilter, + SubscriptionsAcknowledgedNotification, + SubscriptionsAcknowledgedNotificationParams, + SubscriptionsListenRequest, + SubscriptionsListenRequestParams, + Task, + TaskMetadata, + TaskStatus, + TaskStatusNotification, + TaskStatusNotificationParams, TextContent, TextResourceContents, Tool, ToolAnnotations, ToolChoice, + ToolExecution, ToolListChangedNotification, ToolResultContent, ToolsCapability, ToolUseContent, UnsubscribeRequest, UnsubscribeRequestParams, + UnsupportedProtocolVersionErrorData, UrlElicitationCapability, client_notification_adapter, client_request_adapter, @@ -150,10 +190,13 @@ INTERNAL_ERROR, INVALID_PARAMS, INVALID_REQUEST, + JSONRPC_VERSION, METHOD_NOT_FOUND, + MISSING_REQUIRED_CLIENT_CAPABILITY, PARSE_ERROR, REQUEST_CANCELLED, REQUEST_TIMEOUT, + UNSUPPORTED_PROTOCOL_VERSION, URL_ELICITATION_REQUIRED, ErrorData, JSONRPCError, @@ -169,17 +212,28 @@ # Protocol version constants "LATEST_PROTOCOL_VERSION", "DEFAULT_NEGOTIATED_VERSION", + # Reserved request _meta keys + "PROTOCOL_VERSION_META_KEY", + "CLIENT_INFO_META_KEY", + "CLIENT_CAPABILITIES_META_KEY", + "LOG_LEVEL_META_KEY", # Type aliases and variables "ContentBlock", "ElicitRequestedSchema", "ElicitRequestParams", "IncludeContext", + "InputRequest", + "InputRequests", + "InputResponse", + "InputResponses", "LoggingLevel", "ProgressToken", + "ResultType", "Role", "SamplingContent", "SamplingMessageContentBlock", "StopReason", + "TaskStatus", # Base classes "BaseMetadata", "Request", @@ -187,10 +241,12 @@ "Result", "RequestParams", "RequestParamsMeta", + "InputResponseRequestParams", "NotificationParams", "PaginatedRequest", "PaginatedRequestParams", "PaginatedResult", + "CacheableResult", "EmptyResult", # Capabilities "ClientCapabilities", @@ -237,27 +293,40 @@ "ResourceTemplateReference", "Root", "SamplingMessage", + "SubscriptionFilter", + "Task", + "TaskMetadata", + "RelatedTaskMetadata", "Tool", "ToolAnnotations", "ToolChoice", + "ToolExecution", # Requests "CallToolRequest", "CallToolRequestParams", "CompleteRequest", "CompleteRequestParams", + "CancelTaskRequest", + "CancelTaskRequestParams", "CreateMessageRequest", "CreateMessageRequestParams", + "DiscoverRequest", "ElicitRequest", "ElicitRequestFormParams", "ElicitRequestURLParams", "GetPromptRequest", "GetPromptRequestParams", + "GetTaskPayloadRequest", + "GetTaskPayloadRequestParams", + "GetTaskRequest", + "GetTaskRequestParams", "InitializeRequest", "InitializeRequestParams", "ListPromptsRequest", "ListResourcesRequest", "ListResourceTemplatesRequest", "ListRootsRequest", + "ListTasksRequest", "ListToolsRequest", "PingRequest", "ReadResourceRequest", @@ -266,23 +335,35 @@ "SetLevelRequestParams", "SubscribeRequest", "SubscribeRequestParams", + "SubscriptionsListenRequest", + "SubscriptionsListenRequestParams", "UnsubscribeRequest", "UnsubscribeRequestParams", # Results "CallToolResult", + "CancelTaskResult", "CompleteResult", "CreateMessageResult", "CreateMessageResultWithTools", + "CreateTaskResult", + "DiscoverResult", "ElicitResult", "ElicitationRequiredErrorData", "GetPromptResult", + "GetTaskPayloadResult", + "GetTaskResult", "InitializeResult", + "InputRequiredResult", "ListPromptsResult", "ListResourcesResult", "ListResourceTemplatesResult", "ListRootsResult", + "ListTasksResult", "ListToolsResult", "ReadResourceResult", + # Error data payloads + "MissingRequiredClientCapabilityErrorData", + "UnsupportedProtocolVersionErrorData", # Notifications "CancelledNotification", "CancelledNotificationParams", @@ -298,6 +379,10 @@ "ResourceUpdatedNotification", "ResourceUpdatedNotificationParams", "RootsListChangedNotification", + "SubscriptionsAcknowledgedNotification", + "SubscriptionsAcknowledgedNotificationParams", + "TaskStatusNotification", + "TaskStatusNotificationParams", "ToolListChangedNotification", # Union types for request/response routing "ClientNotification", @@ -318,10 +403,13 @@ "INTERNAL_ERROR", "INVALID_PARAMS", "INVALID_REQUEST", + "JSONRPC_VERSION", "METHOD_NOT_FOUND", + "MISSING_REQUIRED_CLIENT_CAPABILITY", "PARSE_ERROR", "REQUEST_CANCELLED", "REQUEST_TIMEOUT", + "UNSUPPORTED_PROTOCOL_VERSION", "URL_ELICITATION_REQUIRED", "ErrorData", "JSONRPCError", diff --git a/src/mcp/types/_spec_names.py b/src/mcp/types/_spec_names.py new file mode 100644 index 0000000000..52c367361f --- /dev/null +++ b/src/mcp/types/_spec_names.py @@ -0,0 +1,159 @@ +"""Deliberate divergences between SDK type names and MCP schema export names. + +Each MCP protocol revision publishes a schema whose exported type names do not +always map one-to-one onto the SDK's public names. This module records those +naming decisions and the reasons behind them — an entry here is a reviewed +decision, not drift. + +Three tables: + +- ``SDK_TO_SCHEMA_RENAMES``: the SDK models this wire shape under a different + name than the schema export (SDK name -> schema name). +- ``SCHEMA_NOT_MODELED``: the schema export is deliberately not modeled as an + SDK type. The value is a short kebab-case reason code naming the modeling + decision (for example ``"named-error-wrapper"``: error wrapper interfaces + whose wire values the generic ``ErrorData`` envelope already covers). +- ``SDK_ONLY_NAMES``: SDK-coined public names that accompany the decisions + above — exports for constructs the schema leaves inline or unmodeled (a + named inline literal, an SDK-side result split, the data payloads of the + unmodeled error wrappers). + +This module is the review record, not the comparison. The comparison lives in +``tests/spec_oracles/``: its ``_harness.py`` builds the schema -> SDK name +pairing by inverting ``SDK_TO_SCHEMA_RENAMES`` (so the rename record has one +home) and is itself authoritative for the version-scoped pairing overrides +and the machinery exemptions, while its ``burndown_allowlist.json`` is +authoritative for the standing divergence findings, version by version. +``tests/types/test_spec_names.py`` asserts the records agree where they +overlap: every deliberately-unmodeled schema export listed here is +acknowledged by the allowlist, and every SDK-only name listed here is one the +comparison also treats as schema-less. + +The ``# OD-13 alternative:`` comment below follows the decision-marker +convention described in the ``mcp.types._types`` module docstring: it names a +reviewed design alternative that was NOT taken, as a record, not a TODO. +""" + +from typing import Final + +SDK_TO_SCHEMA_RENAMES: Final[dict[str, str]] = { + # The schema only hoisted "Error" as a named export in 2025-11-25; the SDK + # name survives from v1.x. + "ErrorData": "Error", + # 2025-11-25 renamed the success envelope and recycled "JSONRPCResponse" + # for the success|error union (see SCHEMA_NOT_MODELED); the SDK keeps its + # original names for both envelope models. + "JSONRPCResponse": "JSONRPCResultResponse", + "JSONRPCError": "JSONRPCErrorResponse", + # The SDK keeps its v1.x notification name; the schema (2025-11-25 and + # later) spells out "Elicitation". + "ElicitCompleteNotification": "ElicitationCompleteNotification", + # The schema (2026-07-28) names the request-scoped `_meta` object; the SDK + # models it as an open TypedDict that types `progressToken` and carries the + # reserved `io.modelcontextprotocol/*` keys as extra items (see the + # `*_META_KEY` constants). + "RequestParamsMeta": "RequestMetaObject", +} + +SCHEMA_NOT_MODELED: Final[dict[str, str]] = { + # 2025-11-25 recycled the export name "JSONRPCResponse" for the union + # JSONRPCResultResponse | JSONRPCErrorResponse. The SDK keeps that name for + # the success-only envelope model, so the union sense is deliberately not + # modeled: JSONRPCMessage parses both members, and session/transport code + # types them as JSONRPCResponse | JSONRPCError. + "JSONRPCResponse": "name-recycled-union", + "JSONRPCBatchRequest": "jsonrpc-batching-2025-03-26-only-never-implemented", + "JSONRPCBatchResponse": "jsonrpc-batching-2025-03-26-only-never-implemented", + # 2026-07-28 JSON aliases; the Python side uses builtins. + "JSONValue": "json-alias-builtins-serve", # Any + "JSONObject": "json-alias-builtins-serve", # dict[str, Any] + "JSONArray": "json-alias-builtins-serve", # list[Any] + # General `_meta` containers are the SDK's open `Meta` mapping + # (`dict[str, Any]`); the schema's named alias for them adds no structure. + # (The request-scoped `RequestMetaObject` IS modeled — see + # SDK_TO_SCHEMA_RENAMES.) + "MetaObject": "nominal-alias-covered-by-dict", + # @internal TS mixin {icons?: Icon[]}; the generated JSON schemas flatten it + # (zero $refs to the $def) and the SDK declares the field inline on each of + # Implementation/Resource/ResourceTemplate/Prompt/Tool. + "Icons": "interface-mixin-flattened", + # Every schema version exports `Cursor` as a bare alias of string; the SDK + # inlines `str` on the consuming pagination fields and dropped the named + # alias from its public surface in v2. + "Cursor": "alias-for-str-inlined-on-consuming-fields-removed-at-v2", + # @internal shared base interface (2025-11-25 and later) whose single field + # `uri` is declared directly on ReadResourceRequestParams, + # SubscribeRequestParams, and UnsubscribeRequestParams. + "ResourceRequestParams": "structural-base-flattened", + # 2026-07-28 grouped exports pairing a result type with its JSON-RPC + # response frame. The SDK keeps the envelope/payload split: the generic + # success envelope (`JSONRPCResponse`) carries any typed result body. + "DiscoverResultResponse": "envelope-wrapper", + "ListResourcesResultResponse": "envelope-wrapper", + "ListResourceTemplatesResultResponse": "envelope-wrapper", + "ReadResourceResultResponse": "envelope-wrapper", + "ListPromptsResultResponse": "envelope-wrapper", + "GetPromptResultResponse": "envelope-wrapper", + "ListToolsResultResponse": "envelope-wrapper", + "CallToolResultResponse": "envelope-wrapper", + "CompleteResultResponse": "envelope-wrapper", + # 2025-11-25 shared base interface for the params classes that accept a + # `task` field; the SDK declares the field directly on each of the four + # task-augmentable params classes. + "TaskAugmentedRequestParams": "structural-base-flattened", + # The named-error wrapper interfaces: documentation vehicles (the five + # standard JSON-RPC ones) or error frames whose data payloads ARE modeled + # (`UnsupportedProtocolVersionErrorData` and friends). The generic + # `ErrorData` envelope plus the code constants cover every wire value. + # Elicitation requested-schema vocabulary: the SDK deliberately keeps + # ElicitRequestedSchema = dict[str, Any] (untyped passthrough); the + # restricted-JSON-Schema union and its members are not modeled. + # OD-13 alternative: model the requested-schema vocabulary as typed classes alongside the untyped field. + "PrimitiveSchemaDefinition": "elicitation-requested-schema-untyped", + "StringSchema": "elicitation-requested-schema-untyped", + "NumberSchema": "elicitation-requested-schema-untyped", + "BooleanSchema": "elicitation-requested-schema-untyped", + "EnumSchema": "elicitation-requested-schema-untyped", + "SingleSelectEnumSchema": "elicitation-requested-schema-untyped", + "UntitledSingleSelectEnumSchema": "elicitation-requested-schema-untyped", + "TitledSingleSelectEnumSchema": "elicitation-requested-schema-untyped", + "MultiSelectEnumSchema": "elicitation-requested-schema-untyped", + "UntitledMultiSelectEnumSchema": "elicitation-requested-schema-untyped", + "TitledMultiSelectEnumSchema": "elicitation-requested-schema-untyped", + "LegacyTitledEnumSchema": "elicitation-requested-schema-untyped", + "ParseError": "named-error-wrapper", + "InvalidRequestError": "named-error-wrapper", + "MethodNotFoundError": "named-error-wrapper", + "InvalidParamsError": "named-error-wrapper", + "InternalError": "named-error-wrapper", + "UnsupportedProtocolVersionError": "named-error-wrapper", + "MissingRequiredClientCapabilityError": "named-error-wrapper", + "URLElicitationRequiredError": "named-error-wrapper", +} + +SDK_ONLY_NAMES: Final[frozenset[str]] = frozenset( + { + # Names the inline "light" | "dark" literal of Icon.theme; no schema + # version exports a named alias for it. + "IconTheme", + # Error-code constants and data payload models for errors the schemas + # define only inside named-error wrapper interfaces (deliberately not + # modeled — see SCHEMA_NOT_MODELED). + "UnsupportedProtocolVersionErrorData", + "UNSUPPORTED_PROTOCOL_VERSION", + "MissingRequiredClientCapabilityErrorData", + "MISSING_REQUIRED_CLIENT_CAPABILITY", + "ElicitationRequiredErrorData", + # The schemas inline the completion request's argument/context objects + # on CompleteRequestParams; the SDK names them. + "CompletionArgument", + "CompletionContext", + # SDK-side split of the schema's CreateMessageResult: the wide + # (2025-11-25+) array-content shape gets its own class so the + # single-block class keeps its v1.x constructor surface. + "CreateMessageResultWithTools", + # Names the untyped requested-schema dict on form-mode elicitation + # params (see the elicitation-requested-schema-untyped rows above). + "ElicitRequestedSchema", + } +) diff --git a/src/mcp/types/_types.py b/src/mcp/types/_types.py index e9d39ef6f3..0b3880932c 100644 --- a/src/mcp/types/_types.py +++ b/src/mcp/types/_types.py @@ -1,3 +1,19 @@ +"""MCP protocol types: one model set spanning every supported protocol version. + +The models are a superset across protocol revisions: each class carries every +field any supported revision defines, and a field one revision requires on the +wire may still be optional here because other revisions lack it entirely. +Docstrings state the per-version wire facts; version-specific emission and +parsing live in ``mcp.types.wire``, not here. + +Comments of the form ``# OD- alternative: `` (and ``# M-`` +in ``mcp.types.jsonrpc``) mark reviewed design decisions at the spot where +they bite: each names the design alternative that was considered and NOT +implemented — the code as written is the accepted choice. The comment text is +the complete statement of the alternative; the id is just a stable label for +referring to the decision in review discussion. They are records, not TODOs. +""" + from __future__ import annotations from typing import Annotated, Any, Generic, Literal, TypeAlias, TypeVar @@ -9,7 +25,13 @@ from mcp.types.jsonrpc import RequestId LATEST_PROTOCOL_VERSION = "2025-11-25" -"""The latest version of the Model Context Protocol. +"""The latest released version of the Model Context Protocol. + +The 2026-07-28 revision also modeled in this package is unreleased: its +schema is published but still subject to change, so it is deliberately newer +than this constant and absent from +`mcp.shared.version.SUPPORTED_PROTOCOL_VERSIONS`. Both move when the +specification releases it. You can find the latest specification at https://modelcontextprotocol.io/specification/latest. """ @@ -28,6 +50,7 @@ Role = Literal["user", "assistant"] IconTheme = Literal["light", "dark"] +"""Theme an icon is designed for. Wire values of ``Icon.theme`` (2025-11-25+).""" class MCPModel(BaseModel): @@ -39,7 +62,53 @@ class MCPModel(BaseModel): Meta: TypeAlias = dict[str, Any] +PROTOCOL_VERSION_META_KEY = "io.modelcontextprotocol/protocolVersion" +"""Reserved request `_meta` key: the MCP protocol version for this request (2026-07-28). + +2026-07-28 requires the client to send it on every request. For the HTTP +transport its value must match the `MCP-Protocol-Version` header. +""" + +CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo" +"""Reserved request `_meta` key: the client `Implementation` making the request (2026-07-28). + +2026-07-28 requires the client to send it on every request; with the +initialize handshake removed there, this key replaces the handshake's +`clientInfo`. +""" + +CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities" +"""Reserved request `_meta` key: the client's per-request `ClientCapabilities` (2026-07-28). + +2026-07-28 requires the client to send it on every request; servers must not +infer capabilities from prior requests. +""" + +LOG_LEVEL_META_KEY = "io.modelcontextprotocol/logLevel" +"""Reserved request `_meta` key: the desired log level for this request (2026-07-28). + +Replaces the former `logging/setLevel` RPC. Deprecated as of protocol version +2026-07-28 (SEP-2577); if absent, the server must not send log notifications +for this request. +""" + + class RequestParamsMeta(TypedDict, extra_items=Any): + """The `_meta` object on request params (schema name: `RequestMetaObject`). + + An open map: arbitrary `_meta` keys — including the reserved + `io.modelcontextprotocol/*` keys — are preserved on round-trip via + ``extra_items=Any``. The reserved keys carry the per-request state that + 2026-07-28 moved into `_meta` (protocol version, client info, client + capabilities, log level); read or set them via the ``*_META_KEY`` + constants. + """ + + # Deliberately no explicit alias: a TypedDict carries no pydantic config of + # its own, so pydantic validates and serializes it with the configuration + # of the model field embedding it (`RequestParams.meta`). That model + # config's `alias_generator=to_camel` is what maps this key to + # "progressToken" on the wire, in both directions. progress_token: NotRequired[ProgressToken] """ If specified, the caller requests out-of-band progress notifications for @@ -54,6 +123,8 @@ class RequestParams(MCPModel): class PaginatedRequestParams(RequestParams): + """Common params for paginated requests.""" + cursor: str | None = None """An opaque token representing the current pagination position. @@ -85,6 +156,11 @@ class PaginatedRequest(Request[PaginatedRequestParams | None, MethodT], Generic[ """Base class for paginated requests, matching the schema's PaginatedRequest interface.""" params: PaginatedRequestParams | None = None + """Pagination params. + + Optional on 2025-11-25 and older wires; required on the 2026-07-28 wire, + where every request must carry `params._meta` with the reserved keys. + """ class Notification(MCPModel, Generic[NotificationParamsT, MethodT]): @@ -94,6 +170,21 @@ class Notification(MCPModel, Generic[NotificationParamsT, MethodT]): params: NotificationParamsT +ResultType = Literal["complete", "input_required"] | str +"""Indicates the type of a Result object, allowing the client to determine how to parse it. + +- "complete": the request completed successfully and the result contains the final content. +- "input_required": the request requires additional input; the result contains an + InputRequiredResult with instructions for the client to provide additional input + before retrying the original request. + +Introduced in protocol 2026-07-28. The union is open: values outside the two named +literals are reserved for future protocol versions and extensions (the tasks extension +reserves "task"). Pre-2026-07-28 peers never send the carrying field; the spec defines +an absent `resultType` as equivalent to "complete". +""" + + class Result(MCPModel): """Base class for JSON-RPC results.""" @@ -103,8 +194,23 @@ class Result(MCPModel): for notes on _meta usage. """ + result_type: ResultType | None = None + """Discriminates complete results from input-required results (2026-07-28). + + `None` means the field was absent on the wire (pre-2026-07-28 peers never + send it), which the spec defines as equivalent to "complete". The + 2026-07-28 wire requires the field on every result. + """ + class PaginatedResult(Result): + """Base class for results of paginated list operations. + + Matches the schema's PaginatedResult interface; concrete list results + (ListToolsResult, ListResourcesResult, ListResourceTemplatesResult, + ListPromptsResult) subclass it. + """ + next_cursor: str | None = None """ An opaque token representing the pagination position after the last returned result. @@ -112,15 +218,43 @@ class PaginatedResult(Result): """ +class CacheableResult(Result): + """Base class for results that carry client-side caching directives (2026-07-28). + + The 2026-07-28 wire requires both fields; 2025-11-25 and earlier revisions + do not define them, so both are optional here. + """ + + ttl_ms: int | None = None + """How long, in milliseconds, the client MAY cache this response before + re-fetching — analogous to HTTP Cache-Control max-age. + + 0 means the response SHOULD be considered immediately stale; a positive value + means the client SHOULD consider the result fresh for that many milliseconds. + Must be non-negative. `None` means unset. + """ + + cache_scope: Literal["public", "private"] | None = None + """Intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + With "public", any client or intermediary (e.g. a shared gateway or proxy) + MAY cache the response and serve it to any user; with "private", only the + requesting user's client MAY cache it, and shared caches MUST NOT serve a + cached copy to a different user. `None` means unset. + """ + + class EmptyResult(Result): - """A response that indicates success but carries no data.""" + """A result that indicates success but carries no data.""" class BaseMetadata(MCPModel): - """Base class for entities with name and optional title fields.""" + """Base class for entities with a programmatic name and an optional display title.""" name: str - """The programmatic name of the entity.""" + """Intended for programmatic or logical use, but used as a display name in past + specs or fallback (if title isn't present).""" title: str | None = None """ @@ -134,24 +268,45 @@ class BaseMetadata(MCPModel): class Icon(MCPModel): - """An icon for display in user interfaces.""" + """An optionally-sized icon that can be displayed in a user interface. + + Added in protocol 2025-11-25; carried in the optional ``icons`` array of + tools, resources, resource templates, prompts, and implementations. Never + present on the wire in earlier protocol versions. + """ src: str - """URL or data URI for the icon.""" + """A standard URI pointing to an icon resource. + + May be an HTTP/HTTPS URL or a ``data:`` URI with Base64-encoded image data. + + Consumers SHOULD take steps to ensure URLs serving icons are from the same + domain as the client/server or a trusted domain, and SHOULD take + appropriate precautions when consuming SVGs, as they can contain + executable JavaScript. + """ mime_type: str | None = None - """Optional MIME type for the icon.""" + """Optional MIME type override if the source MIME type is missing or generic. + + For example: ``"image/png"``, ``"image/jpeg"``, or ``"image/svg+xml"``. + """ sizes: list[str] | None = None - """Optional list of strings specifying icon dimensions (e.g., ["48x48", "96x96"]).""" + """Optional array of strings specifying sizes at which the icon can be used. - theme: IconTheme | None = None - """Optional theme specifier. + Each string should be in WxH format (e.g., ``"48x48"``, ``"96x96"``) or + ``"any"`` for scalable formats like SVG. If not provided, the client + should assume the icon can be used at any size. + """ - `"light"` indicates the icon is designed for a light background, `"dark"` indicates the icon - is designed for a dark background. + theme: IconTheme | None = None + """Optional specifier for the theme this icon is designed for. - See https://modelcontextprotocol.io/specification/2025-11-25/schema#icon for more details. + ``"light"`` indicates the icon is designed to be used with a light + background, and ``"dark"`` indicates the icon is designed to be used with + a dark background. If not provided, the client should assume the icon can + be used with any theme. """ @@ -174,10 +329,19 @@ class Implementation(BaseMetadata): class RootsCapability(MCPModel): - """Capability for root operations.""" + """Capability for root operations. + + Deprecated as a whole in protocol 2026-07-28 (SEP-2577) but remains in the + specification's deprecated-features registry; used on all earlier-version + sessions. + """ list_changed: bool | None = None - """Whether the client supports notifications for changes to the roots list.""" + """Whether the client supports notifications for changes to the roots list. + + Removed in protocol 2026-07-28 (the 2026-07-28 `roots` capability is an + empty object); meaningful on 2025-11-25 and earlier sessions. + """ class SamplingContextCapability(MCPModel): @@ -232,6 +396,83 @@ class SamplingCapability(MCPModel): """ +class TasksListCapability(MCPModel): + """Capability for tasks listing operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksCancelCapability(MCPModel): + """Capability for tasks cancel operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksCreateMessageCapability(MCPModel): + """Capability for task-augmented sampling/createMessage requests. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksSamplingCapability(MCPModel): + """Capability for task-augmented sampling operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + create_message: TasksCreateMessageCapability | None = None + """Whether the client supports task-augmented sampling/createMessage.""" + + +class TasksCreateElicitationCapability(MCPModel): + """Capability for task-augmented elicitation/create requests. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksElicitationCapability(MCPModel): + """Capability for task-augmented elicitation operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + create: TasksCreateElicitationCapability | None = None + """Whether the client supports task-augmented elicitation/create.""" + + +class ClientTasksRequestsCapability(MCPModel): + """Specifies which request types the client can augment with tasks. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + sampling: TasksSamplingCapability | None = None + """Task support for sampling requests.""" + + elicitation: TasksElicitationCapability | None = None + """Task support for elicitation requests.""" + + +class ClientTasksCapability(MCPModel): + """Capability for client tasks operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + list: TasksListCapability | None = None + """Whether this client supports tasks/list.""" + + cancel: TasksCancelCapability | None = None + """Whether this client supports tasks/cancel.""" + + requests: ClientTasksRequestsCapability | None = None + """Specifies which request types can be augmented with tasks.""" + + class ClientCapabilities(MCPModel): """Capabilities a client may support.""" @@ -246,6 +487,45 @@ class ClientCapabilities(MCPModel): """Present if the client supports elicitation from the user.""" roots: RootsCapability | None = None """Present if the client supports listing roots.""" + extensions: dict[str, dict[str, Any]] | None = None + """Optional MCP extensions that the client supports (2026-07-28). + + Keys are extension identifiers (e.g. "io.modelcontextprotocol/oauth-client-credentials"), + values are per-extension settings objects; an empty object indicates support + with no settings. + """ + tasks: ClientTasksCapability | None = None + """Present if the client supports task-augmented requests (2025-11-25 only).""" + + +class UnsupportedProtocolVersionErrorData(MCPModel): + """Error data for the -32004 unsupported-protocol-version error (2026-07-28). + + Servers return this when a request claims a protocol version they do not + support. The client should choose a mutually supported version from + ``supported`` and retry the request. + """ + + supported: list[str] + """Protocol versions the server supports. + + The client should choose a mutually supported version from this list and retry. + """ + + requested: str + """The protocol version that was requested by the client.""" + + +class MissingRequiredClientCapabilityErrorData(MCPModel): + """Error data for the 2026-07-28 MissingRequiredClientCapabilityError (-32003). + + Servers return this when processing a request requires a capability the + client did not declare in the request's `clientCapabilities`. The client + should re-send the request declaring the listed capabilities (or fail). + """ + + required_capabilities: ClientCapabilities + """The capabilities the server requires from the client to process this request.""" class PromptsCapability(MCPModel): @@ -279,6 +559,49 @@ class CompletionsCapability(MCPModel): """Capability for completions operations.""" +class TasksCallCapability(MCPModel): + """Capability for task-augmented tools/call requests. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksToolsCapability(MCPModel): + """Capability for task-augmented tool operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + call: TasksCallCapability | None = None + """Whether the server supports task-augmented tools/call.""" + + +class ServerTasksRequestsCapability(MCPModel): + """Specifies which request types the server can augment with tasks. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + tools: TasksToolsCapability | None = None + """Task support for tool requests.""" + + +class ServerTasksCapability(MCPModel): + """Capability for server tasks operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + list: TasksListCapability | None = None + """Whether this server supports tasks/list.""" + + cancel: TasksCancelCapability | None = None + """Whether this server supports tasks/cancel.""" + + requests: ServerTasksRequestsCapability | None = None + """Specifies which request types can be augmented with tasks.""" + + class ServerCapabilities(MCPModel): """Capabilities that a server may support.""" @@ -300,9 +623,33 @@ class ServerCapabilities(MCPModel): completions: CompletionsCapability | None = None """Present if the server offers autocompletion suggestions for prompts and resources.""" + extensions: dict[str, dict[str, Any]] | None = None + """Optional MCP extensions that the server supports (2026-07-28). + + Keys are extension identifiers (e.g. "io.modelcontextprotocol/tasks"); + values are per-extension settings objects. An empty object indicates + support with no settings. + """ + + tasks: ServerTasksCapability | None = None + """Present if the server supports task-augmented requests (2025-11-25 only).""" + + +# Lifecycle handshake (removed in protocol 2026-07-28). +# +# Protocol 2026-07-28 removed the initialize handshake and ping in favor of +# `server/discover` plus per-request `_meta`. The handshake types stay defined +# because earlier-version sessions still use them; every type the 2026-07-28 +# revision removed — here and elsewhere in this module — carries the same +# "Removed in protocol 2026-07-28" docstring line. +# OD-2 alternative: move removed types to a `mcp.types.legacy` module behind PEP 562 aliases. + class InitializeRequestParams(RequestParams): - """Parameters for the initialize request.""" + """Parameters for the initialize request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ protocol_version: str """The latest version of the Model Context Protocol that the client supports.""" @@ -313,6 +660,9 @@ class InitializeRequestParams(RequestParams): class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]]): """This request is sent from the client to the server when it first connects, asking it to begin initialization. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + The `server/discover` flow plus per-request `_meta` replace the handshake there. """ method: Literal["initialize"] = "initialize" @@ -320,7 +670,12 @@ class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]]) class InitializeResult(Result): - """After receiving an initialize request from the client, the server sends this.""" + """After receiving an initialize request from the client, the server sends this. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + The 2026-07-28 revision replaces the initialize handshake with `server/discover` + (see `DiscoverResult`). + """ protocol_version: str """The version of the Model Context Protocol that the server wants to use.""" @@ -333,6 +688,8 @@ class InitializeResult(Result): class InitializedNotification(Notification[NotificationParams | None, Literal["notifications/initialized"]]): """This notification is sent from the client to the server after initialization has finished. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. """ method: Literal["notifications/initialized"] = "notifications/initialized" @@ -342,12 +699,283 @@ class InitializedNotification(Notification[NotificationParams | None, Literal["n class PingRequest(Request[RequestParams | None, Literal["ping"]]): """A ping, issued by either the server or the client, to check that the other party is still alive. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. """ method: Literal["ping"] = "ping" params: RequestParams | None = None +class DiscoverRequest(Request[RequestParams | None, Literal["server/discover"]]): + """A request from the client asking the server to advertise its supported + protocol versions, capabilities, and other metadata (2026-07-28 only). + + Servers speaking 2026-07-28 MUST implement ``server/discover``; clients MAY + call it but are not required to - version negotiation can also happen + inline via per-request ``_meta``. + """ + + method: Literal["server/discover"] = "server/discover" + params: RequestParams | None = None + """Required on the 2026-07-28 wire, where its ``_meta`` must carry the + reserved ``io.modelcontextprotocol/*`` keys; optional here like every + other wire-required 2026-07-28 field (see the module docstring). + """ + + +class DiscoverResult(CacheableResult): + """The result returned by the server for a `server/discover` request (2026-07-28).""" + + supported_versions: list[str] + """MCP protocol versions this server supports. + + The client should choose a version from this list for use in subsequent requests. + """ + + capabilities: ServerCapabilities + """The capabilities of the server.""" + + server_info: Implementation + """Information about the server software implementation.""" + + instructions: str | None = None + """Natural-language guidance describing the server and its features. + + This can be used by clients to improve an LLM's understanding of available + tools (e.g., by including it in a system prompt). It should focus on + information that helps the model use the server effectively and should not + duplicate information already in tool descriptions. + """ + + +# Tasks (removed in protocol 2026-07-28). +# +# Protocol 2025-11-25 introduced task-augmented requests; protocol 2026-07-28 +# removed them from the core specification (tasks continue as a protocol +# extension). The 2025-11-25 task types are defined here types-only: none of +# their methods appear in the request/notification unions below or in the +# per-version method tables, so they are never dispatched. +# OD-3 alternative: a `mcp/extensions/tasks/` package carrying the extension's task types attaches here. + + +class ToolExecution(MCPModel): + """Execution-related properties for a tool. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating + 2025-11-25 (introduced with the experimental core tasks support; the + tasks extension has no per-tool execution declaration). + """ + + task_support: Literal["forbidden", "optional", "required"] | None = None + """ + Indicates whether this tool supports task-augmented execution. + This allows clients to handle long-running operations through polling + the task system. + + - "forbidden": Tool does not support task-augmented execution (default when absent) + - "optional": Tool may support task-augmented execution + - "required": Tool requires task-augmented execution + + Default: "forbidden" + """ + + +class TaskMetadata(MCPModel): + """Metadata for augmenting a request with task execution. + + Include this in the `task` field of the request parameters. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating + 2025-11-25 (the tasks extension has no request-side task-creation + metadata). + """ + + ttl: int | None = None + """Requested duration in milliseconds to retain task from creation.""" + + +class RelatedTaskMetadata(MCPModel): + """Metadata for associating messages with a task. + + Include this in the ``_meta`` field under the key + ``io.modelcontextprotocol/related-task``. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + task_id: str + """The task identifier this message is associated with.""" + + +TaskStatus = Literal["working", "input_required", "completed", "failed", "cancelled"] +"""The status of a task. + +Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. +""" + + +class Task(MCPModel): + """Data associated with a task. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + task_id: str + """The task identifier.""" + + status: TaskStatus + """Current task state.""" + + status_message: str | None = None + """Optional human-readable message describing the current task state. + + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ + + created_at: str + """ISO 8601 timestamp when the task was created.""" + + last_updated_at: str + """ISO 8601 timestamp when the task was last updated.""" + + ttl: int | None + """Actual retention duration from creation in milliseconds, null for unlimited.""" + + poll_interval: int | None = None + """Suggested polling interval in milliseconds.""" + + +class CreateTaskResult(Result): + """A response to a task-augmented request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + task: Task + + +class GetTaskRequestParams(RequestParams): + """Parameters for a tasks/get request.""" + + task_id: str + """The task identifier to query.""" + + +class GetTaskRequest(Request[GetTaskRequestParams, Literal["tasks/get"]]): + """A request to retrieve the state of a task. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + method: Literal["tasks/get"] = "tasks/get" + params: GetTaskRequestParams + + +class GetTaskResult(Result, Task): + """The response to a tasks/get request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class CancelTaskRequestParams(RequestParams): + """Parameters for a tasks/cancel request.""" + + task_id: str + """The task identifier to cancel.""" + + +class CancelTaskRequest(Request[CancelTaskRequestParams, Literal["tasks/cancel"]]): + """A request to cancel a task. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + method: Literal["tasks/cancel"] = "tasks/cancel" + params: CancelTaskRequestParams + + +class CancelTaskResult(Result, Task): + """The response to a tasks/cancel request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TaskStatusNotificationParams(NotificationParams, Task): + """Parameters for a `notifications/tasks/status` notification.""" + + +class TaskStatusNotification(Notification[TaskStatusNotificationParams, Literal["notifications/tasks/status"]]): + """An optional notification from the receiver to the requestor, informing them that a + task's status has changed. Receivers are not required to send these notifications. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + method: Literal["notifications/tasks/status"] = "notifications/tasks/status" + params: TaskStatusNotificationParams + + +class GetTaskPayloadRequestParams(RequestParams): + """Parameters for a tasks/result request.""" + + task_id: str + """The task identifier to retrieve results for.""" + + +class GetTaskPayloadRequest(Request[GetTaskPayloadRequestParams, Literal["tasks/result"]]): + """A request to retrieve the result of a completed task. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating + 2025-11-25 (the tasks extension delivers terminal payloads inline in + tasks/get responses instead). + """ + + method: Literal["tasks/result"] = "tasks/result" + params: GetTaskPayloadRequestParams + + +class GetTaskPayloadResult(Result): + """The response to a tasks/result request. + + The structure matches the result type of the original request; for example, a + tools/call task would return the CallToolResult structure. The payload arrives + as extra wire fields on this open object, which the SDK's default extra-field + policy does not retain: validating a tasks/result response into this class + keeps only ``_meta``. Callers that know the original request should validate + the response into that request's result type (e.g. ``CallToolResult``) + instead, and custom server handlers should return the original request's + result object directly rather than wrapping it in this class. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class ListTasksRequest(PaginatedRequest[Literal["tasks/list"]]): + """A request to retrieve a list of tasks. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating + 2025-11-25 (the tasks extension deliberately drops tasks/list). + """ + + method: Literal["tasks/list"] = "tasks/list" + + +class ListTasksResult(PaginatedResult): + """The response to a tasks/list request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + tasks: list[Task] + """The list of tasks.""" + + class ProgressNotificationParams(NotificationParams): """Parameters for progress notifications.""" @@ -445,10 +1073,11 @@ class ResourceTemplate(BaseMetadata): """ -class ListResourcesResult(PaginatedResult): +class ListResourcesResult(PaginatedResult, CacheableResult): """The server's response to a resources/list request from the client.""" resources: list[Resource] + """The list of resources the server offers.""" class ListResourceTemplatesRequest(PaginatedRequest[Literal["resources/templates/list"]]): @@ -457,13 +1086,34 @@ class ListResourceTemplatesRequest(PaginatedRequest[Literal["resources/templates method: Literal["resources/templates/list"] = "resources/templates/list" -class ListResourceTemplatesResult(PaginatedResult): +class ListResourceTemplatesResult(PaginatedResult, CacheableResult): """The server's response to a resources/templates/list request from the client.""" resource_templates: list[ResourceTemplate] + """The list of resource templates the server offers.""" + + +class InputResponseRequestParams(RequestParams): + """Base params for client requests that can carry responses to a server's + input requests (2026-07-28 multi-round-trip flow). + + When a request previously returned an InputRequiredResult, the client + retries the original request with these fields populated. Extended by + CallToolRequestParams, GetPromptRequestParams and ReadResourceRequestParams. + """ + input_responses: InputResponses | None = None + """Responses to the server's input requests from the InputRequiredResult. -class ReadResourceRequestParams(RequestParams): + For each key in the InputRequiredResult's inputRequests map, the same key + must appear here with the client's result for that request. + """ + request_state: str | None = None + """Opaque request state from the InputRequiredResult, passed back to the + server verbatim when the client retries the original request.""" + + +class ReadResourceRequestParams(InputResponseRequestParams): """Parameters for reading a resource.""" uri: str @@ -511,10 +1161,11 @@ class BlobResourceContents(ResourceContents): """A base64-encoded string representing the binary data of the item.""" -class ReadResourceResult(Result): +class ReadResourceResult(CacheableResult): """The server's response to a resources/read request from the client.""" contents: list[TextResourceContents | BlobResourceContents] + """The contents of the resource or sub-resources that were read.""" class ResourceListChangedNotification( @@ -529,7 +1180,10 @@ class ResourceListChangedNotification( class SubscribeRequestParams(RequestParams): - """Parameters for subscribing to a resource.""" + """Parameters for subscribing to a resource. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ uri: str """ @@ -541,6 +1195,10 @@ class SubscribeRequestParams(RequestParams): class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscribe"]]): """Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + 2026-07-28 sessions replace per-URI subscribe with ``subscriptions/listen`` + (``SubscriptionsListenRequest``). """ method: Literal["resources/subscribe"] = "resources/subscribe" @@ -548,7 +1206,10 @@ class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscr class UnsubscribeRequestParams(RequestParams): - """Parameters for unsubscribing from a resource.""" + """Parameters for unsubscribing from a resource. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ uri: str """The URI of the resource to unsubscribe from.""" @@ -557,6 +1218,10 @@ class UnsubscribeRequestParams(RequestParams): class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]]): """Sent from the client to request cancellation of resources/updated notifications from the server. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + 2026-07-28 peers manage resource subscriptions declaratively via subscriptions/listen + (SubscriptionsListenRequest) instead. """ method: Literal["resources/unsubscribe"] = "resources/unsubscribe" @@ -584,17 +1249,98 @@ class ResourceUpdatedNotification( params: ResourceUpdatedNotificationParams +class SubscriptionFilter(MCPModel): + """The set of notification types a client may opt in to on a + subscriptions/listen request (2026-07-28). + + Each notification type is opt-in; the server MUST NOT send notification + types the client has not explicitly requested here. The same shape is + echoed back by the server in notifications/subscriptions/acknowledged as + the subset it agreed to honor. + + Extensions merge additional keys into this object on the wire (e.g. the + tasks extension's ``taskIds``), so unknown keys are preserved on + round-trip rather than ignored. + """ + + # OD-9 alternative: a codec-facing extra="allow" parse layer on all models instead of this single carve-out. + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, extra="allow") + + tools_list_changed: bool | None = None + """If true, receive notifications/tools/list_changed.""" + + prompts_list_changed: bool | None = None + """If true, receive notifications/prompts/list_changed.""" + + resources_list_changed: bool | None = None + """If true, receive notifications/resources/list_changed.""" + + resource_subscriptions: list[str] | None = None + """Subscribe to notifications/resources/updated for these resource URIs. + + Replaces the former resources/subscribe RPC. + """ + + +class SubscriptionsListenRequestParams(RequestParams): + """Parameters for a subscriptions/listen request (2026-07-28).""" + + notifications: SubscriptionFilter + """The notifications the client opts in to on this stream. + + The server MUST NOT send notification types the client has not explicitly + requested. + """ + + +class SubscriptionsListenRequest(Request[SubscriptionsListenRequestParams, Literal["subscriptions/listen"]]): + """Sent from the client to open a long-lived channel for receiving notifications + outside the context of a specific request (2026-07-28). + + Replaces the previous HTTP GET endpoint and ensures consistent behavior between + HTTP and STDIO. + """ + + method: Literal["subscriptions/listen"] = "subscriptions/listen" + params: SubscriptionsListenRequestParams + + +class SubscriptionsAcknowledgedNotificationParams(NotificationParams): + """Parameters for a notifications/subscriptions/acknowledged notification.""" + + notifications: SubscriptionFilter + """The subset of requested notification types the server agreed to honor. + + Only includes notification types the server actually supports; if the + client requested an unsupported type (e.g., `promptsListChanged` when the + server has no prompts), it is omitted from this set. + """ + + +class SubscriptionsAcknowledgedNotification( + Notification[ + SubscriptionsAcknowledgedNotificationParams, + Literal["notifications/subscriptions/acknowledged"], + ] +): + """Sent by the server as the first message on a subscriptions/listen stream + to acknowledge that the subscription has been established and to report + which notification types it agreed to honor (2026-07-28). + """ + + method: Literal["notifications/subscriptions/acknowledged"] = "notifications/subscriptions/acknowledged" + params: SubscriptionsAcknowledgedNotificationParams + + class ListPromptsRequest(PaginatedRequest[Literal["prompts/list"]]): """Sent from the client to request a list of prompts and prompt templates.""" method: Literal["prompts/list"] = "prompts/list" -class PromptArgument(MCPModel): - """An argument for a prompt template.""" +class PromptArgument(BaseMetadata): + """Describes an argument that a prompt can accept.""" - name: str - """The name of the argument.""" description: str | None = None """A human-readable description of the argument.""" required: bool | None = None @@ -617,13 +1363,14 @@ class Prompt(BaseMetadata): """ -class ListPromptsResult(PaginatedResult): +class ListPromptsResult(PaginatedResult, CacheableResult): """The server's response to a prompts/list request from the client.""" prompts: list[Prompt] + """The list of prompts and prompt templates the server offers.""" -class GetPromptRequestParams(RequestParams): +class GetPromptRequestParams(InputResponseRequestParams): """Parameters for getting a prompt.""" name: str @@ -694,9 +1441,15 @@ class AudioContent(MCPModel): class ToolUseContent(MCPModel): """Content representing an assistant's request to invoke a tool. - This content type appears in assistant messages when the LLM wants to call a tool - during sampling. The server should execute the tool and return a ToolResultContent - in the next user message. + This content type appears in assistant messages when the LLM wants to call a + tool during sampling-with-tools: in the content of a `sampling/createMessage` + result, and in assistant-role messages replayed in subsequent + `sampling/createMessage` requests. The server should execute the tool and + return a ToolResultContent in the next user message. + + Available on 2025-11-25 and 2026-07-28 sessions only. Deprecated as of + protocol 2026-07-28 (SEP-2577) but remains in the specification for at least + twelve months and stays fully supported here. """ type: Literal["tool_use"] = "tool_use" @@ -712,43 +1465,59 @@ class ToolUseContent(MCPModel): """Arguments to pass to the tool. Must conform to the tool's inputSchema.""" meta: Meta | None = Field(alias="_meta", default=None) - """ - See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - for notes on _meta usage. + """Optional metadata about the tool use. + + Clients SHOULD preserve this field when including tool uses in subsequent + sampling requests to enable caching optimizations. """ class ToolResultContent(MCPModel): - """Content representing the result of a tool execution. + """The result of a tool use, provided by the user back to the assistant. - This content type appears in user messages as a response to a ToolUseContent - from the assistant. It contains the output of executing the requested tool. + Appears in sampling messages (`sampling/createMessage`) as a response to a + ToolUseContent block from the assistant; `tool_use_id` MUST match the `id` of + that block. Requires the `sampling.tools` client capability (2025-11-25 and + later). Deprecated as of protocol 2026-07-28 (SEP-2577) but remains valid on + 2026-07-28 sessions for at least twelve months. """ type: Literal["tool_result"] = "tool_result" """Discriminator for tool result content.""" tool_use_id: str - """The unique identifier that corresponds to the tool call's id field.""" + """The ID of the tool use this result corresponds to. - content: list[ContentBlock] = [] - """ - A list of content objects representing the tool result. - Defaults to empty list if not provided. + This MUST match the ID from a previous ToolUseContent. """ - structured_content: dict[str, Any] | None = None + content: list[ContentBlock] = [] + """The unstructured result content of the tool use. + + Same format as CallToolResult.content: text, images, audio, resource links, + and embedded resources. """ - Optional structured tool output that matches the tool's outputSchema (if defined). + + structured_content: Any = None + """An optional structured result value. + + On 2026-07-28 sessions this can be any JSON value (object, array, string, + number, boolean, or None); 2025-11-25 restricts it to a JSON object. If the + tool defined an outputSchema, this SHOULD conform to that schema. """ is_error: bool | None = None - """Whether the tool execution resulted in an error.""" + """Whether the tool use resulted in an error. - meta: Meta | None = Field(alias="_meta", default=None) + If true, the content typically describes the error that occurred. Absent is + equivalent to false. """ - See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - for notes on _meta usage. + + meta: Meta | None = Field(alias="_meta", default=None) + """Optional metadata about the tool result. + + Clients SHOULD preserve this field when including tool results in subsequent + sampling requests to enable caching optimizations. """ @@ -899,6 +1668,12 @@ class Tool(BaseMetadata): """A human-readable description of the tool.""" input_schema: dict[str, Any] """A JSON Schema object defining the expected parameters for the tool.""" + execution: ToolExecution | None = None + """Execution-related properties for this tool. + + 2025-11-25 only; removed in protocol 2026-07-28 (tasks continue as an + extension). + """ output_schema: dict[str, Any] | None = None """ An optional JSON Schema object defining the structure of the tool's output @@ -915,17 +1690,29 @@ class Tool(BaseMetadata): """ -class ListToolsResult(PaginatedResult): +class ListToolsResult(PaginatedResult, CacheableResult): """The server's response to a tools/list request from the client.""" tools: list[Tool] + """The list of tools the server offers.""" -class CallToolRequestParams(RequestParams): +class CallToolRequestParams(InputResponseRequestParams): """Parameters for calling a tool.""" name: str arguments: dict[str, Any] | None = None + task: TaskMetadata | None = None + """If specified, the caller is requesting task-augmented execution for this request. + + The request will return a CreateTaskResult immediately, and the actual result + can be retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare + support for task augmentation of specific request types in their capabilities. + + 2025-11-25 only; removed in protocol 2026-07-28 (tasks continue as an extension). + """ class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]): @@ -936,12 +1723,36 @@ class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]): class CallToolResult(Result): - """The server's response to a tool call.""" + """The server's response to a tool call. + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `is_error` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ content: list[ContentBlock] - structured_content: dict[str, Any] | None = None - """An optional JSON object that represents the structured result of the tool call.""" + """A list of content objects that represent the unstructured result of the tool call.""" + + # OD-1 alternative: Unset-sentinel default distinguishing wire-absent from + # explicit null (needs client + existing-test carve-outs) + structured_content: Any = None + """An optional JSON value that represents the structured result of the tool call. + + On 2026-07-28 sessions this can be any JSON value (object, array, string, + number, boolean, or null) that conforms to the tool's output schema if one is + defined; 2025-06-18 and 2025-11-25 restrict it to a JSON object on the wire. + """ + is_error: bool = False + """Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + """ class ToolListChangedNotification(Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]]): @@ -957,14 +1768,23 @@ class ToolListChangedNotification(Notification[NotificationParams | None, Litera class SetLevelRequestParams(RequestParams): - """Parameters for setting the logging level.""" + """Parameters for setting the logging level. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ level: LoggingLevel """The level of logging that the client wants to receive from the server.""" class SetLevelRequest(Request[SetLevelRequestParams, Literal["logging/setLevel"]]): - """A request from the client to the server, to enable or adjust logging.""" + """A request from the client to the server, to enable or adjust logging. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating + <= 2025-11-25. On 2026-07-28 sessions the client opts in to log messages + per-request via the `io.modelcontextprotocol/logLevel` key in `_meta` + instead. + """ method: Literal["logging/setLevel"] = "logging/setLevel" params: SetLevelRequestParams @@ -1097,6 +1917,12 @@ class CreateMessageRequestParams(RequestParams): Controls tool usage behavior. Requires clientCapabilities.sampling.tools and the tools parameter to be present. """ + task: TaskMetadata | None = None + """ + If set, requests task-augmented execution for this request (protocol + 2025-11-25 only). Removed in 2026-07-28: receivers on that version MUST + ignore it. + """ class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling/createMessage"]]): @@ -1162,12 +1988,23 @@ class ResourceTemplateReference(MCPModel): """The URI or URI template of the resource.""" +# Deliberately flat on MCPModel, not BaseMetadata, despite the 2025-06-18+ +# schemas declaring the BaseMetadata interface as a base: inheritance would +# reorder dump keys (type, name) -> (name, title, type), changing emitted bytes +# for existing callers. class PromptReference(MCPModel): """Identifies a prompt.""" type: Literal["ref/prompt"] = "ref/prompt" name: str """The name of the prompt or prompt template.""" + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display. + """ class CompletionArgument(MCPModel): @@ -1280,6 +2117,8 @@ class RootsListChangedNotification( This notification should be sent whenever the client adds, removes, or modifies any root. The server should then request an updated list of roots using the ListRootsRequest. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. """ method: Literal["notifications/roots/list_changed"] = "notifications/roots/list_changed" @@ -1331,30 +2170,6 @@ class ElicitCompleteNotification( params: ElicitCompleteNotificationParams -ClientRequest = ( - PingRequest - | InitializeRequest - | CompleteRequest - | SetLevelRequest - | GetPromptRequest - | ListPromptsRequest - | ListResourcesRequest - | ListResourceTemplatesRequest - | ReadResourceRequest - | SubscribeRequest - | UnsubscribeRequest - | CallToolRequest - | ListToolsRequest -) -client_request_adapter = TypeAdapter[ClientRequest](ClientRequest) - - -ClientNotification = ( - CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification -) -client_notification_adapter = TypeAdapter[ClientNotification](ClientNotification) - - # Type for elicitation schema - a JSON Schema dict ElicitRequestedSchema: TypeAlias = dict[str, Any] """Schema for elicitation requests.""" @@ -1379,6 +2194,13 @@ class ElicitRequestFormParams(RequestParams): Only top-level properties are allowed, without nesting. """ + task: TaskMetadata | None = None + """If specified, the caller is requesting task-augmented execution for this request. + + 2025-11-25 sessions only; removed in protocol 2026-07-28 (tasks continue as an + extension). + """ + class ElicitRequestURLParams(RequestParams): """Parameters for URL mode elicitation requests. @@ -1402,6 +2224,13 @@ class ElicitRequestURLParams(RequestParams): The client MUST treat this ID as an opaque value. """ + task: TaskMetadata | None = None + """If specified, the caller is requesting task-augmented execution for this request. + + 2025-11-25 sessions only; removed in protocol 2026-07-28 (tasks continue as an + extension). + """ + # Union type for elicitation request parameters ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams @@ -1436,21 +2265,153 @@ class ElicitResult(Result): class ElicitationRequiredErrorData(MCPModel): - """Error data for URLElicitationRequiredError. + """Error data for the URL-elicitation-required error (code -32042, ``URL_ELICITATION_REQUIRED``). Servers return this when a request cannot be processed until one or more URL mode elicitations are completed. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. """ elicitations: list[ElicitRequestURLParams] """List of URL mode elicitations that must be completed.""" +InputRequest: TypeAlias = CreateMessageRequest | ListRootsRequest | ElicitRequest +"""A single server-initiated input request embedded in a multi-round-trip flow (2026-07-28). + +Values of the ``InputRequests`` map carried by ``InputRequiredResult.input_requests``. +On 2026-07-28 sessions these embedded payloads replace the standalone +server-to-client JSON-RPC requests of earlier protocol versions; each member's +required ``method`` literal is the discriminating tag. +""" + +InputRequests: TypeAlias = dict[str, InputRequest] +"""A map of server-initiated requests that the client must fulfill (2026-07-28). + +Keys are server-assigned identifiers; values are the embedded request payloads +(`CreateMessageRequest | ListRootsRequest | ElicitRequest`). Carried by +`InputRequiredResult.input_requests` in the multi-round-trip (MRTR) flow; the +`io.modelcontextprotocol/tasks` extension reuses the same type for its +`inputRequests` fields. +""" + +InputResponse: TypeAlias = CreateMessageResult | CreateMessageResultWithTools | ListRootsResult | ElicitResult +"""A client response to a single server-initiated input request (2026-07-28, MRTR). + +Values never travel as top-level JSON-RPC results: they appear as entries of an +``InputResponses`` map — in ``inputResponses`` on retried client requests, and in +the tasks extension's ``tasks/update`` params. ``CreateMessageResultWithTools`` is +the SDK's array-content split of the schema's single ``CreateMessageResult`` arm; +the wire union has exactly three arms. +""" + +InputResponses: TypeAlias = dict[str, InputResponse] +"""A map of client responses to server-initiated input requests (2026-07-28, MRTR). + +Keys correspond to the keys of the ``InputRequests`` map the server sent in its +``InputRequiredResult``; values are the client's result for each request. Reused +verbatim by the ``io.modelcontextprotocol/tasks`` extension (``tasks/update`` +params), which keys responses to currently-outstanding input requests. +""" + + +class InputRequiredResult(Result): + """The server needs additional input before the original request can complete (2026-07-28). + + Returned in place of the normal result of an interactive client request + (`tools/call`, `prompts/get`, `resources/read`). The client fulfills + `input_requests` and retries the original request, carrying the matching + responses and the echoed `request_state`. + + At least one of `input_requests` / `request_state` is present on the wire + (spec MUST; not enforced by the model — inbound stays lenient). + """ + + input_requests: InputRequests | None = None + """Requests issued by the server that must be completed before the client can retry the original request. + + Keys are server-assigned identifiers; values are the embedded request payloads. + """ + + request_state: str | None = None + """Opaque state to pass back to the server when the client retries the original request. + + The client must treat this as an opaque blob and must not interpret it in any way. + """ + + +# Deferred-annotation completion: InputResponseRequestParams (and its consumers) +# reference InputResponses, which is only bound above. Explicit rebuilds keep +# model completion at import time rather than first use. +InputResponseRequestParams.model_rebuild() +ReadResourceRequestParams.model_rebuild() +GetPromptRequestParams.model_rebuild() +CallToolRequestParams.model_rebuild() + +# Top-level message unions, declared last so every member class is bound. +# Membership is the superset across all known protocol versions; which members +# are valid on a given session's negotiated version is recorded in the +# per-version method tables (mcp.types._versions), never enforced here — +# inbound parsing stays superset-lenient on every session. + +ClientRequest = ( + PingRequest + | InitializeRequest + | CompleteRequest + | SetLevelRequest + | GetPromptRequest + | ListPromptsRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | CallToolRequest + | ListToolsRequest + | DiscoverRequest + | SubscriptionsListenRequest +) +"""Union of client-to-server request payloads across all supported protocol versions. + +The 2025-11-25 task requests are deliberately excluded (types-only, never +dispatched). +""" + +# OD-12 alternative: method-discriminated adapter (rejects method-less dicts). +client_request_adapter = TypeAdapter[ClientRequest](ClientRequest) + + +ClientNotification = ( + CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification +) +"""Notifications sent from the client to the server. + +All four members are valid on every released version (2024-11-05 through +2025-11-25); on 2026-07-28 sessions only ``CancelledNotification`` and +``ProgressNotification`` are. The 2025-11-25 ``TaskStatusNotification`` is +deliberately excluded (types-only, never dispatched). +""" + +# OD-12 alternative: method-discriminated adapter (rejects method-less dicts). +client_notification_adapter = TypeAdapter[ClientNotification](ClientNotification) + + ClientResult = EmptyResult | CreateMessageResult | CreateMessageResultWithTools | ListRootsResult | ElicitResult client_result_adapter = TypeAdapter[ClientResult](ClientResult) ServerRequest = PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest +"""Union of standalone JSON-RPC requests a server can send to a client. + +Live on 2024-11-05 through 2025-11-25 sessions only: the 2026-07-28 protocol +removes the standalone server-to-client request channel. On 2026-07-28 +sessions, sampling, roots, and elicitation requests are instead embedded in +``InputRequiredResult.input_requests``, and ping is removed entirely, so the +server-request method set for that version is empty. +""" + +# OD-12 alternative: method-discriminated adapter (rejects method-less dicts). server_request_adapter = TypeAdapter[ServerRequest](ServerRequest) @@ -1463,13 +2424,22 @@ class ElicitationRequiredErrorData(MCPModel): | ToolListChangedNotification | PromptListChangedNotification | ElicitCompleteNotification + | SubscriptionsAcknowledgedNotification ) +"""Union of server-to-client notification payloads across all supported protocol versions. + +The 2025-11-25 ``TaskStatusNotification`` is deliberately excluded (types-only, +never dispatched). +""" + +# OD-12 alternative: method-discriminated adapter (rejects method-less dicts). server_notification_adapter = TypeAdapter[ServerNotification](ServerNotification) ServerResult = ( EmptyResult | InitializeResult + | DiscoverResult | CompleteResult | GetPromptResult | ListPromptsResult @@ -1478,5 +2448,17 @@ class ElicitationRequiredErrorData(MCPModel): | ReadResourceResult | CallToolResult | ListToolsResult + | InputRequiredResult ) +"""Union of every result payload a server can return for a client request. + +Spans all supported protocol versions: `InitializeResult` is only valid on +pre-2026-07-28 sessions; `DiscoverResult` and `InputRequiredResult` only on +2026-07-28 sessions. Member order matters only between `EmptyResult` and +`InputRequiredResult`, the two members with no required fields: a payload that +sets none of `InputRequiredResult`'s own fields (e.g. a bare ``{}``) parses as +whichever of the two comes first, so `EmptyResult` is placed before it. Every +other member has a required field and is matched by content regardless of +position. +""" server_result_adapter = TypeAdapter[ServerResult](ServerResult) diff --git a/src/mcp/types/_versions.py b/src/mcp/types/_versions.py new file mode 100644 index 0000000000..508a49e60d --- /dev/null +++ b/src/mcp/types/_versions.py @@ -0,0 +1,164 @@ +"""Per-version method tables for the MCP wire protocol. + +For each known protocol version (see ``mcp.shared.version.KNOWN_PROTOCOL_VERSIONS``), +these tables record which JSON-RPC method strings exist in that version's +schema, split by direction and message kind. They are plain data: nothing here +parses, validates, or dispatches. + +Derivation rule, applied per version: the ``method`` literals of the request +and notification types reachable from that version's published schema unions, +minus the four 2025-11-25 ``tasks/*`` request methods (the SDK defines the +task types but never dispatches them), plus nothing. The +2025-11-25 ``notifications/tasks/status`` method is a schema fact and stays +listed even though the SDK's notification unions exclude its type. +""" + +from collections.abc import Mapping +from typing import Final + +# 2024-11-05 +_CLIENT_REQUESTS_2024_11_05: Final[frozenset[str]] = frozenset( + { + "completion/complete", + "initialize", + "logging/setLevel", + "ping", + "prompts/get", + "prompts/list", + "resources/list", + "resources/read", + "resources/subscribe", + "resources/templates/list", + "resources/unsubscribe", + "tools/call", + "tools/list", + } +) +_CLIENT_NOTIFICATIONS_2024_11_05: Final[frozenset[str]] = frozenset( + { + "notifications/cancelled", + "notifications/initialized", + "notifications/progress", + "notifications/roots/list_changed", + } +) +_SERVER_REQUESTS_2024_11_05: Final[frozenset[str]] = frozenset( + { + "ping", + "roots/list", + "sampling/createMessage", + } +) +_SERVER_NOTIFICATIONS_2024_11_05: Final[frozenset[str]] = frozenset( + { + "notifications/cancelled", + "notifications/message", + "notifications/progress", + "notifications/prompts/list_changed", + "notifications/resources/list_changed", + "notifications/resources/updated", + "notifications/tools/list_changed", + } +) + +# 2025-03-26: identical method sets to 2024-11-05 (the revision changed type +# shapes, not the method surface). +_CLIENT_REQUESTS_2025_03_26: Final[frozenset[str]] = _CLIENT_REQUESTS_2024_11_05 +_CLIENT_NOTIFICATIONS_2025_03_26: Final[frozenset[str]] = _CLIENT_NOTIFICATIONS_2024_11_05 +_SERVER_REQUESTS_2025_03_26: Final[frozenset[str]] = _SERVER_REQUESTS_2024_11_05 +_SERVER_NOTIFICATIONS_2025_03_26: Final[frozenset[str]] = _SERVER_NOTIFICATIONS_2024_11_05 + +# 2025-06-18: adds elicitation/create (server -> client). +_CLIENT_REQUESTS_2025_06_18: Final[frozenset[str]] = _CLIENT_REQUESTS_2024_11_05 +_CLIENT_NOTIFICATIONS_2025_06_18: Final[frozenset[str]] = _CLIENT_NOTIFICATIONS_2024_11_05 +_SERVER_REQUESTS_2025_06_18: Final[frozenset[str]] = _SERVER_REQUESTS_2024_11_05 | {"elicitation/create"} +_SERVER_NOTIFICATIONS_2025_06_18: Final[frozenset[str]] = _SERVER_NOTIFICATIONS_2024_11_05 + +# 2025-11-25: adds notifications/tasks/status (both directions) and +# notifications/elicitation/complete (server -> client). The four tasks/* +# request methods the schema also adds are excluded per the derivation rule. +_CLIENT_REQUESTS_2025_11_25: Final[frozenset[str]] = _CLIENT_REQUESTS_2024_11_05 +_CLIENT_NOTIFICATIONS_2025_11_25: Final[frozenset[str]] = _CLIENT_NOTIFICATIONS_2024_11_05 | { + "notifications/tasks/status", +} +_SERVER_REQUESTS_2025_11_25: Final[frozenset[str]] = _SERVER_REQUESTS_2025_06_18 +_SERVER_NOTIFICATIONS_2025_11_25: Final[frozenset[str]] = _SERVER_NOTIFICATIONS_2024_11_05 | { + "notifications/elicitation/complete", + "notifications/tasks/status", +} + +# 2026-07-28: removes the lifecycle handshake (initialize, ping, +# notifications/initialized), logging/setLevel, the resources subscribe pair, +# the roots and tasks methods, and the entire server -> client request +# channel; adds server/discover, subscriptions/listen, and +# notifications/subscriptions/acknowledged. +_CLIENT_REQUESTS_2026_07_28: Final[frozenset[str]] = frozenset( + { + "completion/complete", + "prompts/get", + "prompts/list", + "resources/list", + "resources/read", + "resources/templates/list", + "server/discover", + "subscriptions/listen", + "tools/call", + "tools/list", + } +) +_CLIENT_NOTIFICATIONS_2026_07_28: Final[frozenset[str]] = frozenset( + { + "notifications/cancelled", + "notifications/progress", + } +) +_SERVER_REQUESTS_2026_07_28: Final[frozenset[str]] = frozenset() +_SERVER_NOTIFICATIONS_2026_07_28: Final[frozenset[str]] = frozenset( + { + "notifications/cancelled", + "notifications/elicitation/complete", + "notifications/message", + "notifications/progress", + "notifications/prompts/list_changed", + "notifications/resources/list_changed", + "notifications/resources/updated", + "notifications/subscriptions/acknowledged", + "notifications/tools/list_changed", + } +) + +CLIENT_REQUEST_METHODS: Final[Mapping[str, frozenset[str]]] = { + "2024-11-05": _CLIENT_REQUESTS_2024_11_05, + "2025-03-26": _CLIENT_REQUESTS_2025_03_26, + "2025-06-18": _CLIENT_REQUESTS_2025_06_18, + "2025-11-25": _CLIENT_REQUESTS_2025_11_25, + "2026-07-28": _CLIENT_REQUESTS_2026_07_28, +} +"""Client-to-server request methods defined at each protocol version.""" + +CLIENT_NOTIFICATION_METHODS: Final[Mapping[str, frozenset[str]]] = { + "2024-11-05": _CLIENT_NOTIFICATIONS_2024_11_05, + "2025-03-26": _CLIENT_NOTIFICATIONS_2025_03_26, + "2025-06-18": _CLIENT_NOTIFICATIONS_2025_06_18, + "2025-11-25": _CLIENT_NOTIFICATIONS_2025_11_25, + "2026-07-28": _CLIENT_NOTIFICATIONS_2026_07_28, +} +"""Client-to-server notification methods defined at each protocol version.""" + +SERVER_REQUEST_METHODS: Final[Mapping[str, frozenset[str]]] = { + "2024-11-05": _SERVER_REQUESTS_2024_11_05, + "2025-03-26": _SERVER_REQUESTS_2025_03_26, + "2025-06-18": _SERVER_REQUESTS_2025_06_18, + "2025-11-25": _SERVER_REQUESTS_2025_11_25, + "2026-07-28": _SERVER_REQUESTS_2026_07_28, +} +"""Server-to-client request methods defined at each protocol version.""" + +SERVER_NOTIFICATION_METHODS: Final[Mapping[str, frozenset[str]]] = { + "2024-11-05": _SERVER_NOTIFICATIONS_2024_11_05, + "2025-03-26": _SERVER_NOTIFICATIONS_2025_03_26, + "2025-06-18": _SERVER_NOTIFICATIONS_2025_06_18, + "2025-11-25": _SERVER_NOTIFICATIONS_2025_11_25, + "2026-07-28": _SERVER_NOTIFICATIONS_2026_07_28, +} +"""Server-to-client notification methods defined at each protocol version.""" diff --git a/src/mcp/types/_wire_base.py b/src/mcp/types/_wire_base.py new file mode 100644 index 0000000000..0f1295123a --- /dev/null +++ b/src/mcp/types/_wire_base.py @@ -0,0 +1,35 @@ +"""Shared pydantic bases for the per-version wire-shape model packages. + +Every model in the ``mcp.types.v*`` packages builds on one of the two bases +here, so the five packages cannot silently diverge in model configuration. +There is deliberately no alias generator: each wire name in a version package +is an explicit ``Field(alias=...)``, so the package file shows exactly what +goes on the wire and cannot inherit serialization behavior from elsewhere. +""" + +from pydantic import BaseModel, ConfigDict + + +class WireModel(BaseModel): + """Base for version-package models: unknown fields are dropped. + + ``extra="ignore"`` is a deliberate divergence from the schemas, which + declare most wire objects open to extra fields. Closed models are what + make a field the target protocol revision never defined register as a + loss when a value is revalidated for that revision's wire, and they keep + an empty result dumping as exactly ``{}`` (deployed peers reject an empty + result that carries extra keys). + """ + + model_config = ConfigDict(populate_by_name=True, extra="ignore") + + +class OpenWireModel(BaseModel): + """Base for ``_meta`` carrier models: unknown fields are retained. + + Unknown ``_meta`` keys must survive a validate -> re-dump round trip at + every protocol revision, so the classes a ``_meta`` field references stay + open. + """ + + model_config = ConfigDict(populate_by_name=True, extra="allow") diff --git a/src/mcp/types/jsonrpc.py b/src/mcp/types/jsonrpc.py index 14743c33b0..2d7965cb84 100644 --- a/src/mcp/types/jsonrpc.py +++ b/src/mcp/types/jsonrpc.py @@ -1,44 +1,119 @@ -"""This module follows the JSON-RPC 2.0 specification: https://www.jsonrpc.org/specification.""" +"""This module follows the JSON-RPC 2.0 specification: https://www.jsonrpc.org/specification. + +The ``# M-2 alternative:`` comment below follows the decision-marker +convention described in the ``mcp.types._types`` module docstring: it names a +reviewed design alternative that was NOT taken, as a record, not a TODO. +""" from __future__ import annotations -from typing import Annotated, Any, Literal +from typing import Annotated, Any, Final, Literal from pydantic import BaseModel, Field, TypeAdapter RequestId = Annotated[int, Field(strict=True)] | str -"""The ID of a JSON-RPC request.""" +"""A uniquely identifying ID for a request in JSON-RPC. + +Identical in every supported protocol version: a string or an integer (the JSON +form of every schema version pins the numeric kind to integer; null is never +allowed). The strict ``int`` arm disables pydantic cross-coercion so a parsed id +keeps the exact wire type the peer sent (``"7"`` stays ``str``; ``True`` is +rejected), which is what lets a response echo the id back unchanged. +""" + +JSONRPC_VERSION: Final[Literal["2.0"]] = "2.0" +"""The JSON-RPC protocol version carried by every MCP message envelope. + +Identical in every MCP protocol version (2024-11-05 through 2026-07-28): the +``jsonrpc`` field of every envelope type is ``Literal["2.0"]`` and always holds +exactly this value. +""" class JSONRPCRequest(BaseModel): """A JSON-RPC request that expects a response.""" jsonrpc: Literal["2.0"] + """The JSON-RPC protocol version. Always "2.0".""" + id: RequestId + """A uniquely identifying ID for this request, established by the sender.""" + method: str + """The name of the method being invoked.""" + params: dict[str, Any] | None = None + """The parameter object for the method, if any.""" class JSONRPCNotification(BaseModel): """A JSON-RPC notification which does not expect a response.""" jsonrpc: Literal["2.0"] + """The JSON-RPC protocol version. Always "2.0".""" + method: str + """The method name of the notification.""" + params: dict[str, Any] | None = None + """The notification's parameters as an untyped mapping. + + Typed access goes through the `Notification` payload models in `mcp.types`; + the envelope deliberately leaves this untyped. + """ -# TODO(Marcelo): This is actually not correct. A JSONRPCResponse is the union of a successful response and an error. class JSONRPCResponse(BaseModel): - """A successful (non-error) response to a request.""" + """A successful (non-error) response to a request. + + Wire shape is identical across all supported protocol versions. The spec named + this type ``JSONRPCResponse`` through 2025-06-18 and renamed it + ``JSONRPCResultResponse`` in 2025-11-25 (recycling ``JSONRPCResponse`` for the + success|error union); the SDK keeps its original name, recorded in the + spec-name divergence map. + """ jsonrpc: Literal["2.0"] + """The JSON-RPC protocol version. Always "2.0".""" + id: RequestId + """The id of the request this response answers.""" + result: dict[str, Any] + """The result payload as a raw JSON object. + + The envelope deliberately leaves the payload untyped: typed result models are + validated and serialized at the session layer, then wrapped in this envelope. + """ # MCP-specific error codes in the range [-32000, -32099] URL_ELICITATION_REQUIRED = -32042 -"""Error code indicating that a URL mode elicitation is required before the request can be processed.""" +"""Error code indicating that a URL mode elicitation is required before the request can be processed. + +Removed in protocol 2026-07-28; used on 2025-11-25 sessions (the 2026-07-28 +input-required flow delivers URL elicitations inside results instead). +""" + +MISSING_REQUIRED_CLIENT_CAPABILITY = -32003 +"""Error code returned when a server requires a client capability that was +not declared in the request's `clientCapabilities` (protocol 2026-07-28). + +The error's `data.requiredCapabilities` lists the missing capabilities; see +`MissingRequiredClientCapabilityErrorData` in `mcp.types`. For HTTP, the +response status code MUST be 400 Bad Request. +""" + +UNSUPPORTED_PROTOCOL_VERSION = -32004 +"""Error code returned when the request's protocol version is not supported by the server. + +Introduced in protocol 2026-07-28: returned when the version a request claims (the +``io.modelcontextprotocol/protocolVersion`` ``_meta`` key, which must match the HTTP +``MCP-Protocol-Version`` header) is unknown to the server or unsupported. For HTTP, +the response status code MUST be ``400 Bad Request``. The error's ``data`` member +carries an ``UnsupportedProtocolVersionErrorData`` payload listing the versions the +server supports, so the client can retry with a mutually supported one. +""" # SDK error codes CONNECTION_CLOSED = -32000 @@ -47,10 +122,43 @@ class JSONRPCResponse(BaseModel): # Standard JSON-RPC error codes PARSE_ERROR = -32700 +"""Standard JSON-RPC error code: invalid JSON was received. + +Returned when the receiver cannot parse the JSON text of a message. The +2026-07-28 schema also publishes a typed ``ParseError`` error-object shape for +this code; the SDK deliberately keeps the generic ``ErrorData`` envelope and +represents a parse error as ``ErrorData(code=PARSE_ERROR, message=...)``. +""" + INVALID_REQUEST = -32600 +"""Standard JSON-RPC error code: the message is not a valid request object. + +Returned when a message's structure does not conform to the JSON-RPC 2.0 +specification requirements for a request (e.g. missing required fields like +``jsonrpc`` or ``method``, or invalid types for those fields). +""" + METHOD_NOT_FOUND = -32601 +"""Error code: the requested method does not exist or is not available. + +Since protocol 2026-07-28 this explicitly includes methods gated behind a +server capability the server did not advertise; a request that requires a +CLIENT capability the client did not declare is signalled by code -32003 +(``MISSING_REQUIRED_CLIENT_CAPABILITY``) instead. +""" + INVALID_PARAMS = -32602 +"""Standard JSON-RPC error code: the method parameters are invalid or malformed.""" + INTERNAL_ERROR = -32603 +"""Standard JSON-RPC error code: an internal error occurred on the receiver. + +Returned when the receiver encounters an unexpected condition that prevents +it from fulfilling the request. Identical in every MCP protocol version +(2024-11-05 through 2026-07-28). The 2026-07-28 schema's ``InternalError`` +wrapper interface is deliberately not modeled: error responses use the +generic ``ErrorData`` envelope, and ``ErrorData.code`` carries this value. +""" class ErrorData(BaseModel): @@ -76,9 +184,30 @@ class JSONRPCError(BaseModel): """A response to a request that indicates an error occurred.""" jsonrpc: Literal["2.0"] + """The JSON-RPC protocol version. Always "2.0".""" + + # M-2 alternative: id gains "= None" — 2025-11-25 and later schemas also allow omitting the member. id: RequestId | None + """The id of the request this error responds to. + + ``None`` is the JSON-RPC 2.0 ``"id": null`` form, used when no request id + could be determined (e.g. a parse error). The member itself is always present + on the wire; SDK-generated error responses always set an id. + """ + error: ErrorData + """The error that occurred.""" JSONRPCMessage = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError +"""Any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent. + +One envelope for every protocol version: the 2025-11-25 schema restructure +(`JSONRPCResponse = JSONRPCResultResponse | JSONRPCErrorResponse`) changed the +schema's union nesting and member names, not the wire shape of a frame. The +2025-03-26 batch frames (JSON arrays of messages) are not members and are not +supported. +""" + jsonrpc_message_adapter: TypeAdapter[JSONRPCMessage] = TypeAdapter(JSONRPCMessage) +"""TypeAdapter for parsing wire frames into JSONRPCMessage at the transport boundary.""" diff --git a/src/mcp/types/v2024_11_05/__init__.py b/src/mcp/types/v2024_11_05/__init__.py new file mode 100644 index 0000000000..952c13d0f4 --- /dev/null +++ b/src/mcp/types/v2024_11_05/__init__.py @@ -0,0 +1,1452 @@ +"""Wire-shape models for MCP protocol version 2024-11-05 — not user-facing API. + +The oldest supported revision, so this module defines the complete model set; +each later version module defines only what its revision added or changed and +imports everything else from the version module that last defined it. + +Consumed by ``mcp.types.wire``: ``serialize_for`` re-validates each outbound +monolith dump through the negotiated version's models, importing the version +module lazily on first boundary use (never at ``import mcp.types``). + +Initially generated from the pinned 2024-11-05 schema (spec commit +6d441518de) with datamodel-code-generator 0.57.0 plus a +mechanical delta split, then hand-validated against the pinned schema. +Maintained as ordinary source: the effective surface is asserted equal to the +pinned schema by ``tests/types/test_version_surfaces.py``, so a drifting edit +fails CI. + +The models are deliberately closed (``extra="ignore"``) even where the schema +declares an object open to extra fields — see ``mcp.types._wire_base`` for +the rationale. The classes kept open are commented in place. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import ConfigDict, Field + +from mcp.types._wire_base import OpenWireModel, WireModel + +__all__ = [ + "AnnotatedModel", + "BlobResourceContents", + "CallToolRequest", + "CallToolResult", + "CancelledNotification", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteResult", + "CreateMessageRequest", + "CreateMessageResult", + "Cursor", + "EmbeddedResource", + "EmptyResult", + "GetPromptRequest", + "GetPromptResult", + "ImageContent", + "Implementation", + "InitializeRequest", + "InitializeResult", + "InitializedNotification", + "JSONRPCError", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "ModelHint", + "ModelPreferences", + "Notification", + "PaginatedRequest", + "PaginatedResult", + "PingRequest", + "ProgressNotification", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceResult", + "Request", + "RequestId", + "Resource", + "ResourceContents", + "ResourceListChangedNotification", + "ResourceReference", + "ResourceTemplate", + "ResourceUpdatedNotification", + "Result", + "Role", + "Root", + "RootsListChangedNotification", + "SamplingMessage", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "SubscribeRequest", + "TextContent", + "TextResourceContents", + "Tool", + "ToolListChangedNotification", + "UnsubscribeRequest", +] + + +class BlobResourceContents(WireModel): + blob: str + """ + A base64-encoded string representing the binary data of the item. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: str + """ + The URI of this resource. + """ + + +class CallToolRequestParams(WireModel): + arguments: dict[str, Any] | None = None + name: str + + +class CallToolRequest(WireModel): + """Used by the client to invoke a tool provided by the server.""" + + method: Literal["tools/call"] + params: CallToolRequestParams + + +class Roots(WireModel): + """Present if the client supports listing roots.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether the client supports notifications for changes to the roots list. + """ + + +class ClientCapabilities(WireModel): + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any client can define its own, additional capabilities. + """ + + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ + roots: Roots | None = None + """ + Present if the client supports listing roots. + """ + sampling: dict[str, Any] | None = None + """ + Present if the client supports sampling from an LLM. + """ + + +class Argument(WireModel): + """The argument's information""" + + name: str + """ + The name of the argument + """ + value: str + """ + The value of the argument to use for completion matching. + """ + + +class Completion(WireModel): + has_more: Annotated[bool | None, Field(alias="hasMore")] = None + """ + Indicates whether there are additional completion options beyond those provided in the current response, even if the + exact total is unknown. + """ + total: int | None = None + """ + The total number of completion options available. This can exceed the number of values actually sent in the + response. + """ + values: list[str] + """ + An array of completion values. Must not exceed 100 items. + """ + + +class CompleteResult(WireModel): + """The server's response to a completion/complete request""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + completion: Completion + + +Cursor: TypeAlias = str + + +class GetPromptRequestParams(WireModel): + arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ + name: str + """ + The name of the prompt or prompt template. + """ + + +class GetPromptRequest(WireModel): + """Used by the client to get a prompt provided by the server.""" + + method: Literal["prompts/get"] + params: GetPromptRequestParams + + +class Implementation(WireModel): + """Describes the name and version of an MCP implementation.""" + + name: str + version: str + + +class InitializeRequestParams(WireModel): + capabilities: ClientCapabilities + client_info: Annotated[Implementation, Field(alias="clientInfo")] + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older + versions as well. + """ + + +class InitializeRequest(WireModel): + """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" + + method: Literal["initialize"] + params: InitializeRequestParams + + +class Params6(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their + notifications. + """ + + +class InitializedNotification(WireModel): + """This notification is sent from the client to the server after initialization has finished.""" + + method: Literal["notifications/initialized"] + params: Params6 | None = None + + +class Error(WireModel): + code: int + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class JSONRPCNotification(WireModel): + """A notification which does not expect a response.""" + + jsonrpc: Literal["2.0"] + method: str + params: Params6 | None = None + + +class Params9(WireModel): + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class ListPromptsRequest(WireModel): + """Sent from the client to request a list of prompts and prompt templates the server has.""" + + method: Literal["prompts/list"] + params: Params9 | None = None + + +class ListResourceTemplatesRequest(WireModel): + """Sent from the client to request a list of resource templates the server has.""" + + method: Literal["resources/templates/list"] + params: Params9 | None = None + + +class ListResourcesRequest(WireModel): + """Sent from the client to request a list of resources the server has.""" + + method: Literal["resources/list"] + params: Params9 | None = None + + +class ListToolsRequest(WireModel): + """Sent from the client to request a list of tools the server has.""" + + method: Literal["tools/list"] + params: Params9 | None = None + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +class LoggingMessageNotificationParams(WireModel): + data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ + level: LoggingLevel + """ + The severity of this log message. + """ + logger: str | None = None + """ + An optional name of the logger issuing this message. + """ + + +class LoggingMessageNotification(WireModel): + """Notification of a log message passed from server to client. If no logging/setLevel request has been sent from + the client, the server MAY decide which messages to send automatically. + """ + + method: Literal["notifications/message"] + params: LoggingMessageNotificationParams + + +class ModelHint(WireModel): + """Hints to use for model selection. + + Keys not declared here are currently left unspecified by the spec and are up + to the client to interpret. + """ + + name: str | None = None + """ + A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it + fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` + """ + + +class ModelPreferences(WireModel): + """The server's preferences for model selection, requested of the client during sampling. + + Because LLMs can vary along multiple dimensions, choosing the "best" model is + rarely straightforward. Different models excel in different areas—some are + faster but less capable, others are more capable but more expensive, and so + on. This interface allows servers to express their priorities across multiple + dimensions to help clients make an appropriate selection for their use case. + + These preferences are always advisory. The client MAY ignore them. It is also + up to the client to decide how to interpret these preferences and how to + balance them against other considerations. + """ + + cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + """ + hints: list[ModelHint] | None = None + """ + Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. + """ + intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + """ + speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + """ + + +class NotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their + notifications. + """ + + +class Notification(WireModel): + method: str + params: NotificationParams | None = None + + +class PaginatedRequestParams(WireModel): + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class PaginatedRequest(WireModel): + method: str + params: PaginatedRequestParams | None = None + + +class PaginatedResult(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + + +ProgressToken: TypeAlias = str | int + + +class PromptArgument(WireModel): + """Describes an argument that a prompt can accept.""" + + description: str | None = None + """ + A human-readable description of the argument. + """ + name: str + """ + The name of the argument. + """ + required: bool | None = None + """ + Whether this argument must be provided. + """ + + +class PromptListChangedNotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their + notifications. + """ + + +class PromptListChangedNotification(WireModel): + """An optional notification from the server to the client, informing it that the list of prompts it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ + + method: Literal["notifications/prompts/list_changed"] + params: PromptListChangedNotificationParams | None = None + + +class PromptReference(WireModel): + """Identifies a prompt.""" + + name: str + """ + The name of the prompt or prompt template + """ + type: Literal["ref/prompt"] + + +class ReadResourceRequestParams(WireModel): + uri: str + """ + The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class ReadResourceRequest(WireModel): + """Sent from the client to the server, to read a specific resource URI.""" + + method: Literal["resources/read"] + params: ReadResourceRequestParams + + +class Meta(OpenWireModel): + progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by + notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent + notifications. The receiver is not obligated to provide these notifications. + """ + + +class RequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class Request(WireModel): + method: str + params: RequestParams | None = None + + +RequestId: TypeAlias = str | int + + +class ResourceContents(WireModel): + """The contents of a specific resource or sub-resource.""" + + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: str + """ + The URI of this resource. + """ + + +class ResourceListChangedNotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their + notifications. + """ + + +class ResourceListChangedNotification(WireModel): + """An optional notification from the server to the client, informing it that the list of resources it can read + from has changed. This may be issued by servers without any previous subscription from the client. + """ + + method: Literal["notifications/resources/list_changed"] + params: ResourceListChangedNotificationParams | None = None + + +class ResourceReference(WireModel): + """A reference to a resource or resource template definition.""" + + type: Literal["ref/resource"] + uri: str + """ + The URI or URI template of the resource. + """ + + +class ResourceUpdatedNotificationParams(WireModel): + uri: str + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually + subscribed to. + """ + + +class ResourceUpdatedNotification(WireModel): + """A notification from the server to the client, informing it that a resource has changed and may need to be read + again. This should only be sent if the client previously sent a resources/subscribe request. + """ + + method: Literal["notifications/resources/updated"] + params: ResourceUpdatedNotificationParams + + +class Result(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + + +Role: TypeAlias = Literal["assistant", "user"] + + +class Root(WireModel): + """Represents a root directory or file that the server can operate on.""" + + name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ + uri: str + """ + The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ + + +class RootsListChangedNotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their + notifications. + """ + + +class RootsListChangedNotification(WireModel): + """A notification from the client to the server, informing it that the list of roots has changed. + This notification should be sent whenever the client adds, removes, or modifies any root. + The server should then request an updated list of roots using the ListRootsRequest. + """ + + method: Literal["notifications/roots/list_changed"] + params: RootsListChangedNotificationParams | None = None + + +class Prompts(WireModel): + """Present if the server offers any prompt templates.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the prompt list. + """ + + +class Resources(WireModel): + """Present if the server offers any resources to read.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the resource list. + """ + subscribe: bool | None = None + """ + Whether this server supports subscribing to resource updates. + """ + + +class Tools(WireModel): + """Present if the server offers any tools to call.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the tool list. + """ + + +class ServerCapabilities(WireModel): + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any server can define its own, additional capabilities. + """ + + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ + logging: dict[str, Any] | None = None + """ + Present if the server supports sending log messages to the client. + """ + prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ + resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ + tools: Tools | None = None + """ + Present if the server offers any tools to call. + """ + + +class SetLevelRequestParams(WireModel): + level: LoggingLevel + """ + The level of logging that the client wants to receive from the server. The server should send all logs at this level + and higher (i.e., more severe) to the client as notifications/message. + """ + + +class SetLevelRequest(WireModel): + """A request from the client to the server, to enable or adjust logging.""" + + method: Literal["logging/setLevel"] + params: SetLevelRequestParams + + +class SubscribeRequestParams(WireModel): + uri: str + """ + The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class SubscribeRequest(WireModel): + """Sent from the client to request resources/updated notifications from the server whenever a particular resource + changes. + """ + + method: Literal["resources/subscribe"] + params: SubscribeRequestParams + + +class Annotations(WireModel): + audience: list[Role] | None = None + """ + Describes who the intended customer of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). + """ + priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None + """ + Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + """ + + +class TextContent(WireModel): + """Text provided to or from an LLM.""" + + annotations: Annotations | None = None + text: str + """ + The text content of the message. + """ + type: Literal["text"] + + +class TextResourceContents(WireModel): + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ + uri: str + """ + The URI of this resource. + """ + + +class InputSchema(WireModel): + """A JSON Schema object defining the expected parameters for the tool.""" + + # Stays open: schema keywords beyond the declared properties ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class Tool(WireModel): + """Definition for a tool the client can call.""" + + description: str | None = None + """ + A human-readable description of the tool. + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + """ + name: str + """ + The name of the tool. + """ + + +class ToolListChangedNotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their + notifications. + """ + + +class ToolListChangedNotification(WireModel): + """An optional notification from the server to the client, informing it that the list of tools it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ + + method: Literal["notifications/tools/list_changed"] + params: ToolListChangedNotificationParams | None = None + + +class UnsubscribeRequestParams(WireModel): + uri: str + """ + The URI of the resource to unsubscribe from. + """ + + +class UnsubscribeRequest(WireModel): + """Sent from the client to request cancellation of resources/updated notifications from the server. This should + follow a previous resources/subscribe request. + """ + + method: Literal["resources/unsubscribe"] + params: UnsubscribeRequestParams + + +class AnnotatedModel(WireModel): + """Base for objects that include optional annotations for the client. The client can use annotations to inform + how objects are used or displayed + """ + + annotations: Annotations | None = None + + +class CancelledNotificationParams(WireModel): + reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ + request_id: Annotated[RequestId, Field(alias="requestId")] + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + """ + + +class CancelledNotification(WireModel): + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this + notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + + A client MUST NOT attempt to cancel its `initialize` request. + """ + + method: Literal["notifications/cancelled"] + params: CancelledNotificationParams + + +class CompleteRequestParams(WireModel): + argument: Argument + """ + The argument's information + """ + ref: PromptReference | ResourceReference + + +class CompleteRequest(WireModel): + """A request from the client to the server, to ask for completion options.""" + + method: Literal["completion/complete"] + params: CompleteRequestParams + + +class EmbeddedResource(WireModel): + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ + + annotations: Annotations | None = None + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +# Not in this version's schema (2025-06-18 introduced it): the SDK emits +# this content block to older peers unchanged rather than refusing. The only +# content arms deliberately absent from older packages are the tool blocks +# added in 2025-11-25. +class ResourceLink(WireModel): + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["resource_link"] + uri: str + """ + The URI of this resource. + """ + + +EmptyResult: TypeAlias = Result + + +class ImageContent(WireModel): + """An image provided to or from an LLM.""" + + annotations: Annotations | None = None + data: str + """ + The base64-encoded image data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ + type: Literal["image"] + + +# Not in this version's schema (2025-03-26 introduced it): the SDK emits +# this content block to older peers unchanged rather than refusing. The only +# content arms deliberately absent from older packages are the tool blocks +# added in 2025-11-25. +class AudioContent(WireModel): + """Audio provided to or from an LLM.""" + + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: str + """ + The base64-encoded audio data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the audio. Different providers may support different audio types. + """ + type: Literal["audio"] + + +class InitializeResult(WireModel): + """After receiving an initialize request from the client, the server sends this response.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + capabilities: ServerCapabilities + instructions: str | None = None + """ + Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought + of like a "hint" to the model. For example, this information MAY be added to the system prompt. + """ + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The version of the Model Context Protocol that the server wants to use. This may not match the version that the + client requested. If the client cannot support this version, it MUST disconnect. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class JSONRPCError(WireModel): + """A response to a request that indicates an error occurred.""" + + error: Error + id: RequestId + jsonrpc: Literal["2.0"] + + +class JSONRPCRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class JSONRPCRequest(WireModel): + """A request that expects a response.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: JSONRPCRequestParams | None = None + + +class JSONRPCResponse(WireModel): + """A successful (non-error) response to a request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class ListRootsRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class ListRootsRequest(WireModel): + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ + + method: Literal["roots/list"] + params: ListRootsRequestParams | None = None + + +class ListRootsResult(WireModel): + """The client's response to a roots/list request from the server. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + roots: list[Root] + + +class ListToolsResult(WireModel): + """The server's response to a tools/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tools: list[Tool] + + +class PingRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class PingRequest(WireModel): + """A ping, issued by either the server or the client, to check that the other party is still alive. The receiver + must promptly respond, or else may be disconnected. + """ + + method: Literal["ping"] + params: PingRequestParams | None = None + + +class ProgressNotificationParams(WireModel): + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """ + The progress token which was given in the initial request, used to associate this notification with the request that + is proceeding. + """ + total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ + + +class ProgressNotification(WireModel): + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + + method: Literal["notifications/progress"] + params: ProgressNotificationParams + + +class Prompt(WireModel): + """A prompt or prompt template that the server offers.""" + + arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ + description: str | None = None + """ + An optional description of what this prompt provides + """ + name: str + """ + The name of the prompt or prompt template. + """ + + +class PromptMessage(WireModel): + """Describes a message returned as part of a prompt. + + This is similar to `SamplingMessage`, but also supports the embedding of + resources from the MCP server. + """ + + # Carries arms beyond this version's schema; see the class comments above. + content: TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + role: Role + + +class ReadResourceResult(WireModel): + """The server's response to a resources/read request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + contents: list[TextResourceContents | BlobResourceContents] + + +class Resource(WireModel): + """A known resource that the server is capable of reading.""" + + annotations: Annotations | None = None + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + A human-readable name for this resource. + + This can be used by clients to populate UI elements. + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + uri: str + """ + The URI of this resource. + """ + + +class ResourceTemplate(WireModel): + """A template description for resources available on the server.""" + + annotations: Annotations | None = None + description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type for all resources that match this template. This should only be included if all resources matching + this template have the same type. + """ + name: str + """ + A human-readable name for the type of resource this template refers to. + + This can be used by clients to populate UI elements. + """ + uri_template: Annotated[str, Field(alias="uriTemplate")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +class SamplingMessage(WireModel): + """Describes a message issued to or received from an LLM API.""" + + # Carries arms beyond this version's schema; see the class comments above. + content: TextContent | ImageContent | AudioContent + role: Role + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification +) + + +class CallToolResult(WireModel): + """The server's response to a tool call. + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + # Carries arms beyond this version's schema; see the class comments above. + content: list[TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource] + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + """ + + +ClientNotification: TypeAlias = ( + CancelledNotification | InitializedNotification | ProgressNotification | RootsListChangedNotification +) + +ClientRequest: TypeAlias = ( + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | SetLevelRequest + | CompleteRequest +) + + +class CreateMessageRequestParams(WireModel): + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The + client MAY ignore this request. + """ + max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens + than requested. + """ + messages: list[SamplingMessage] + metadata: dict[str, Any] | None = None + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ + temperature: float | None = None + + +class CreateMessageRequest(WireModel): + """A request from the server to sample an LLM via the client. The client has full discretion over which model to + select. The client should also inform the user before beginning sampling, to allow them to inspect the request + (human in the loop) and decide whether to approve it. + """ + + method: Literal["sampling/createMessage"] + params: CreateMessageRequestParams + + +class CreateMessageResult(WireModel): + """The client's response to a sampling/create_message request from the server. The client should inform the user + before returning the sampled message, to allow them to inspect the response (human in the loop) and decide + whether to allow the server to see it. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + # Carries arms beyond this version's schema; see the class comments above. + content: TextContent | ImageContent | AudioContent + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + The reason why sampling stopped, if known. + """ + + +class GetPromptResult(WireModel): + """The server's response to a prompts/get request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + description: str | None = None + """ + An optional description for the prompt. + """ + messages: list[PromptMessage] + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError + + +class ListPromptsResult(WireModel): + """The server's response to a prompts/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + prompts: list[Prompt] + + +class ListResourceTemplatesResult(WireModel): + """The server's response to a resources/templates/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + + +class ListResourcesResult(WireModel): + """The server's response to a resources/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resources: list[Resource] + + +ServerRequest: TypeAlias = PingRequest | CreateMessageRequest | ListRootsRequest + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) + +ClientResult: TypeAlias = Result | CreateMessageResult | ListRootsResult diff --git a/src/mcp/types/v2025_03_26/__init__.py b/src/mcp/types/v2025_03_26/__init__.py new file mode 100644 index 0000000000..ed665e407e --- /dev/null +++ b/src/mcp/types/v2025_03_26/__init__.py @@ -0,0 +1,479 @@ +"""Wire-shape models for MCP protocol version 2025-03-26 — not user-facing API. + +Defines only what this revision added or changed relative to 2024-11-05; +everything else is imported from the version module that last defined it, so +every import line names the module where a model is defined. +``REMOVED_FROM_PREVIOUS_VERSION`` lists the names 2024-11-05 defined that +this revision dropped. + +Consumed by ``mcp.types.wire``: ``serialize_for`` re-validates each outbound +monolith dump through the negotiated version's models, importing the version +module lazily on first boundary use (never at ``import mcp.types``). + +Initially generated from the pinned 2025-03-26 schema (spec commit +6d441518de) with datamodel-code-generator 0.57.0 plus a +mechanical delta split, then hand-validated against the pinned schema. +Maintained as ordinary source: the effective surface is asserted equal to the +pinned schema by ``tests/types/test_version_surfaces.py``, so a drifting edit +fails CI. + +The models are deliberately closed (``extra="ignore"``) even where the schema +declares an object open to extra fields — see ``mcp.types._wire_base`` for +the rationale. The classes kept open are commented in place. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Final, Literal, TypeAlias + +from pydantic import Field + +from mcp.types._wire_base import WireModel + +# Unchanged since 2024-11-05: +from mcp.types.v2024_11_05 import ( + Annotations, + AudioContent, + BlobResourceContents, + CallToolRequest, + CallToolResult, + CancelledNotification, + ClientCapabilities, + ClientRequest, + ClientResult, + CompleteRequest, + CompleteResult, + CreateMessageRequest, + CreateMessageResult, + Cursor, + EmbeddedResource, + EmptyResult, + GetPromptRequest, + GetPromptResult, + ImageContent, + Implementation, + InitializedNotification, + InitializeRequest, + InputSchema, + JSONRPCError, + JSONRPCNotification, + JSONRPCRequest, + JSONRPCResponse, + ListPromptsRequest, + ListPromptsResult, + ListResourcesRequest, + ListResourcesResult, + ListResourceTemplatesRequest, + ListResourceTemplatesResult, + ListRootsRequest, + ListRootsResult, + ListToolsRequest, + LoggingLevel, + LoggingMessageNotification, + ModelHint, + ModelPreferences, + Notification, + PaginatedRequest, + PaginatedResult, + PingRequest, + ProgressToken, + Prompt, + PromptArgument, + PromptListChangedNotification, + PromptMessage, + PromptReference, + Prompts, + ReadResourceRequest, + ReadResourceResult, + Request, + RequestId, + Resource, + ResourceContents, + ResourceListChangedNotification, + ResourceReference, + Resources, + ResourceTemplate, + ResourceUpdatedNotification, + Result, + Role, + Root, + RootsListChangedNotification, + SamplingMessage, + ServerRequest, + SetLevelRequest, + SubscribeRequest, + TextContent, + TextResourceContents, + ToolListChangedNotification, + Tools, + UnsubscribeRequest, +) + +REMOVED_FROM_PREVIOUS_VERSION: Final[frozenset[str]] = frozenset( + { + "AnnotatedModel", + } +) + +__all__ = [ + "Annotations", + "AudioContent", + "BlobResourceContents", + "CallToolRequest", + "CallToolResult", + "CancelledNotification", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteResult", + "CreateMessageRequest", + "CreateMessageResult", + "Cursor", + "EmbeddedResource", + "EmptyResult", + "GetPromptRequest", + "GetPromptResult", + "ImageContent", + "Implementation", + "InitializeRequest", + "InitializeResult", + "InitializedNotification", + "JSONRPCBatchRequest", + "JSONRPCBatchResponse", + "JSONRPCError", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "ModelHint", + "ModelPreferences", + "Notification", + "PaginatedRequest", + "PaginatedResult", + "PingRequest", + "ProgressNotification", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceResult", + "Request", + "RequestId", + "Resource", + "ResourceContents", + "ResourceListChangedNotification", + "ResourceReference", + "ResourceTemplate", + "ResourceUpdatedNotification", + "Result", + "Role", + "Root", + "RootsListChangedNotification", + "SamplingMessage", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "SubscribeRequest", + "TextContent", + "TextResourceContents", + "Tool", + "ToolAnnotations", + "ToolListChangedNotification", + "UnsubscribeRequest", +] + +# --- New in 2025-03-26 --- + + +class ToolAnnotations(WireModel): + """Additional properties describing a Tool to clients. + + NOTE: all properties in ToolAnnotations are **hints**. + They are not guaranteed to provide a faithful description of + tool behavior (including descriptive properties like `title`). + + Clients should never make tool use decisions based on ToolAnnotations + received from untrusted servers. + """ + + destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None + """ + If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: true + """ + idempotent_hint: Annotated[bool | None, Field(alias="idempotentHint")] = None + """ + If true, calling the tool repeatedly with the same arguments + will have no additional effect on the its environment. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: false + """ + open_world_hint: Annotated[bool | None, Field(alias="openWorldHint")] = None + """ + If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + + Default: true + """ + read_only_hint: Annotated[bool | None, Field(alias="readOnlyHint")] = None + """ + If true, the tool does not modify its environment. + + Default: false + """ + title: str | None = None + """ + A human-readable title for the tool. + """ + + +# --- Changed in 2025-03-26 --- + + +class ServerCapabilities(WireModel): + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any server can define its own, additional capabilities. + """ + + completions: dict[str, Any] | None = None + """ + Present if the server supports argument autocompletion suggestions. + """ + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ + logging: dict[str, Any] | None = None + """ + Present if the server supports sending log messages to the client. + """ + prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ + resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ + tools: Tools | None = None + """ + Present if the server offers any tools to call. + """ + + +class InitializeResult(WireModel): + """After receiving an initialize request from the client, the server sends this response.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + capabilities: ServerCapabilities + instructions: str | None = None + """ + Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought + of like a "hint" to the model. For example, this information MAY be added to the system prompt. + """ + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The version of the Model Context Protocol that the server wants to use. This may not match the version that the + client requested. If the client cannot support this version, it MUST disconnect. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class ProgressNotificationParams(WireModel): + message: str | None = None + """ + An optional message describing the current progress. + """ + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """ + The progress token which was given in the initial request, used to associate this notification with the request that + is proceeding. + """ + total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ + + +class ProgressNotification(WireModel): + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + + method: Literal["notifications/progress"] + params: ProgressNotificationParams + + +class Tool(WireModel): + """Definition for a tool the client can call.""" + + annotations: ToolAnnotations | None = None + """ + Optional additional tool information. + """ + description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a + "hint" to the model. + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + """ + name: str + """ + The name of the tool. + """ + + +class ListToolsResult(WireModel): + """The server's response to a tools/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their + responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tools: list[Tool] + + +# Not in this version's schema (2025-06-18 introduced it): the SDK emits +# this content block to older peers unchanged rather than refusing. The only +# content arms deliberately absent from older packages are the tool blocks +# added in 2025-11-25. +class ResourceLink(WireModel): + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["resource_link"] + uri: str + """ + The URI of this resource. + """ + + +# --- Aliases new or changed in 2025-03-26 --- +# (defined last: an alias right-hand side evaluates its referents at import) + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification +) + +ClientNotification: TypeAlias = ( + CancelledNotification | InitializedNotification | ProgressNotification | RootsListChangedNotification +) + +JSONRPCBatchRequest: TypeAlias = list[JSONRPCRequest | JSONRPCNotification] + +JSONRPCBatchResponse: TypeAlias = list[JSONRPCResponse | JSONRPCError] + +JSONRPCMessage: TypeAlias = ( + JSONRPCRequest + | JSONRPCNotification + | list[JSONRPCRequest | JSONRPCNotification] + | JSONRPCResponse + | JSONRPCError + | list[JSONRPCResponse | JSONRPCError] +) + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) diff --git a/src/mcp/types/v2025_06_18/__init__.py b/src/mcp/types/v2025_06_18/__init__.py new file mode 100644 index 0000000000..0b11f30423 --- /dev/null +++ b/src/mcp/types/v2025_06_18/__init__.py @@ -0,0 +1,1205 @@ +"""Wire-shape models for MCP protocol version 2025-06-18 — not user-facing API. + +Defines only what this revision added or changed relative to 2025-03-26; +everything else is imported from the version module that last defined it, so +every import line names the module where a model is defined. +``REMOVED_FROM_PREVIOUS_VERSION`` lists the names 2025-03-26 defined that +this revision dropped. + +Consumed by ``mcp.types.wire``: ``serialize_for`` re-validates each outbound +monolith dump through the negotiated version's models, importing the version +module lazily on first boundary use (never at ``import mcp.types``). + +Initially generated from the pinned 2025-06-18 schema (spec commit +6d441518de) with datamodel-code-generator 0.57.0 plus a +mechanical delta split, then hand-validated against the pinned schema. +Maintained as ordinary source: the effective surface is asserted equal to the +pinned schema by ``tests/types/test_version_surfaces.py``, so a drifting edit +fails CI. + +The models are deliberately closed (``extra="ignore"``) even where the schema +declares an object open to extra fields — see ``mcp.types._wire_base`` for +the rationale. The classes kept open are commented in place. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Final, Literal, TypeAlias + +from pydantic import ConfigDict, Field + +from mcp.types._wire_base import WireModel + +# Unchanged since 2024-11-05: +from mcp.types.v2024_11_05 import ( + Argument, + CallToolRequest, + CancelledNotification, + CompleteResult, + Cursor, + EmptyResult, + GetPromptRequest, + InputSchema, + JSONRPCError, + JSONRPCRequest, + JSONRPCResponse, + ListRootsRequest, + LoggingLevel, + LoggingMessageNotification, + ModelHint, + ModelPreferences, + Notification, + PaginatedRequest, + PaginatedResult, + PingRequest, + ProgressToken, + PromptListChangedNotification, + ReadResourceRequest, + Request, + RequestId, + ResourceListChangedNotification, + ResourceUpdatedNotification, + Result, + Role, + Roots, + RootsListChangedNotification, + SetLevelRequest, + SubscribeRequest, + ToolListChangedNotification, + UnsubscribeRequest, +) + +# Unchanged since 2025-03-26: +from mcp.types.v2025_03_26 import ( + ProgressNotification, + ServerCapabilities, + ServerNotification, + ToolAnnotations, +) + +REMOVED_FROM_PREVIOUS_VERSION: Final[frozenset[str]] = frozenset( + { + "JSONRPCBatchRequest", + "JSONRPCBatchResponse", + "ResourceReference", + } +) + +__all__ = [ + "Annotations", + "AudioContent", + "BaseMetadata", + "BlobResourceContents", + "BooleanSchema", + "CallToolRequest", + "CallToolResult", + "CancelledNotification", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteResult", + "ContentBlock", + "CreateMessageRequest", + "CreateMessageResult", + "Cursor", + "ElicitRequest", + "ElicitResult", + "EmbeddedResource", + "EmptyResult", + "EnumSchema", + "GetPromptRequest", + "GetPromptResult", + "ImageContent", + "Implementation", + "InitializeRequest", + "InitializeResult", + "InitializedNotification", + "JSONRPCError", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "ModelHint", + "ModelPreferences", + "Notification", + "NumberSchema", + "PaginatedRequest", + "PaginatedResult", + "PingRequest", + "PrimitiveSchemaDefinition", + "ProgressNotification", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceResult", + "Request", + "RequestId", + "Resource", + "ResourceContents", + "ResourceLink", + "ResourceListChangedNotification", + "ResourceTemplate", + "ResourceTemplateReference", + "ResourceUpdatedNotification", + "Result", + "Role", + "Root", + "RootsListChangedNotification", + "SamplingMessage", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "StringSchema", + "SubscribeRequest", + "TextContent", + "TextResourceContents", + "Tool", + "ToolAnnotations", + "ToolListChangedNotification", + "UnsubscribeRequest", +] + +# --- New in 2025-06-18 --- + + +class BaseMetadata(WireModel): + """Base interface for metadata with name (identifier) and title (display name) properties.""" + + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class BooleanSchema(WireModel): + default: bool | None = None + description: str | None = None + title: str | None = None + type: Literal["boolean"] + + +class Context(WireModel): + """Additional, optional context for completions""" + + arguments: dict[str, str] | None = None + """ + Previously-resolved variables in a URI template or prompt. + """ + + +class ElicitResult(WireModel): + """The client's response to an elicitation request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + action: Literal["accept", "cancel", "decline"] + """ + The user action in response to the elicitation. + - "accept": User submitted the form/confirmed the action + - "decline": User explicitly declined the action + - "cancel": User dismissed without making an explicit choice + """ + # Deliberate deviation from the pinned schema.json, which renders the + # value union's number arm as "integer" — its schema.ts source types form + # answers string | number | boolean, so fractional answers are legal wire + # values. The float arm follows schema.ts; the generated oracle keeps the + # rendering verbatim and the surface test pins this annotation separately. + content: dict[str, str | int | float | bool] | None = None + """ + The submitted form data, only present when action is "accept". + Contains values matching the requested schema. + """ + + +class EnumSchema(WireModel): + description: str | None = None + enum: list[str] + enum_names: Annotated[list[str] | None, Field(alias="enumNames")] = None + title: str | None = None + type: Literal["string"] + + +class Params7(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class Params10(WireModel): + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class NumberSchema(WireModel): + description: str | None = None + # Deliberate deviation from the pinned schema.json, which renders these + # bounds as "integer" — schema.ts types them number (JSON Schema + # minimum/maximum are numbers; the schema describes number fields too). + # The float arms follow schema.ts; the generated oracle keeps the + # rendering verbatim and the surface test pins these annotations + # separately. + maximum: int | float | None = None + minimum: int | float | None = None + title: str | None = None + type: Literal["integer", "number"] + + +class ResourceTemplateReference(WireModel): + """A reference to a resource or resource template definition.""" + + type: Literal["ref/resource"] + uri: str + """ + The URI or URI template of the resource. + """ + + +class StringSchema(WireModel): + description: str | None = None + format: Literal["date", "date-time", "email", "uri"] | None = None + max_length: Annotated[int | None, Field(alias="maxLength")] = None + min_length: Annotated[int | None, Field(alias="minLength")] = None + title: str | None = None + type: Literal["string"] + + +class OutputSchema(WireModel): + """An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + """ + + # Stays open: schema keywords beyond the declared properties ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class RequestedSchema(WireModel): + """A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + properties: dict[str, PrimitiveSchemaDefinition] + required: list[str] | None = None + type: Literal["object"] + + +class ElicitRequestParams(WireModel): + message: str + """ + The message to present to the user. + """ + requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + """ + A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + +class ElicitRequest(WireModel): + """A request from the server to elicit additional information from the user via the client.""" + + method: Literal["elicitation/create"] + params: ElicitRequestParams + + +# --- Changed in 2025-06-18 --- + + +class BlobResourceContents(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + blob: str + """ + A base64-encoded string representing the binary data of the item. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: str + """ + The URI of this resource. + """ + + +class ClientCapabilities(WireModel): + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any client can define its own, additional capabilities. + """ + + elicitation: dict[str, Any] | None = None + """ + Present if the client supports elicitation from the server. + """ + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ + roots: Roots | None = None + """ + Present if the client supports listing roots. + """ + sampling: dict[str, Any] | None = None + """ + Present if the client supports sampling from an LLM. + """ + + +class Implementation(WireModel): + """Describes the name and version of an MCP implementation, with an optional title for UI representation.""" + + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + version: str + + +class InitializeRequestParams(WireModel): + capabilities: ClientCapabilities + client_info: Annotated[Implementation, Field(alias="clientInfo")] + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older + versions as well. + """ + + +class InitializeRequest(WireModel): + """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" + + method: Literal["initialize"] + params: InitializeRequestParams + + +class InitializedNotification(WireModel): + """This notification is sent from the client to the server after initialization has finished.""" + + method: Literal["notifications/initialized"] + params: Params7 | None = None + + +class JSONRPCNotification(WireModel): + """A notification which does not expect a response.""" + + jsonrpc: Literal["2.0"] + method: str + params: Params7 | None = None + + +class ListPromptsRequest(WireModel): + """Sent from the client to request a list of prompts and prompt templates the server has.""" + + method: Literal["prompts/list"] + params: Params10 | None = None + + +class ListResourceTemplatesRequest(WireModel): + """Sent from the client to request a list of resource templates the server has.""" + + method: Literal["resources/templates/list"] + params: Params10 | None = None + + +class ListResourcesRequest(WireModel): + """Sent from the client to request a list of resources the server has.""" + + method: Literal["resources/list"] + params: Params10 | None = None + + +class ListToolsRequest(WireModel): + """Sent from the client to request a list of tools the server has.""" + + method: Literal["tools/list"] + params: Params10 | None = None + + +class PromptArgument(WireModel): + """Describes an argument that a prompt can accept.""" + + description: str | None = None + """ + A human-readable description of the argument. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + required: bool | None = None + """ + Whether this argument must be provided. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class PromptReference(WireModel): + """Identifies a prompt.""" + + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["ref/prompt"] + + +class ResourceContents(WireModel): + """The contents of a specific resource or sub-resource.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: str + """ + The URI of this resource. + """ + + +class Root(WireModel): + """Represents a root directory or file that the server can operate on.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ + uri: str + """ + The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ + + +class TextResourceContents(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ + uri: str + """ + The URI of this resource. + """ + + +class Annotations(WireModel): + """Optional annotations for the client. The client can use annotations to inform how objects are used or + displayed + """ + + audience: list[Role] | None = None + """ + Describes who the intended customer of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). + """ + last_modified: Annotated[str | None, Field(alias="lastModified")] = None + """ + The moment the resource was last modified, as an ISO 8601 formatted string. + + Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + + Examples: last activity timestamp in an open file, timestamp when the resource + was attached, etc. + """ + priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None + """ + Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + """ + + +class AudioContent(WireModel): + """Audio provided to or from an LLM.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: str + """ + The base64-encoded audio data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the audio. Different providers may support different audio types. + """ + type: Literal["audio"] + + +class CompleteRequestParams(WireModel): + argument: Argument + """ + The argument's information + """ + context: Context | None = None + """ + Additional, optional context for completions + """ + ref: PromptReference | ResourceTemplateReference + + +class CompleteRequest(WireModel): + """A request from the client to the server, to ask for completion options.""" + + method: Literal["completion/complete"] + params: CompleteRequestParams + + +class EmbeddedResource(WireModel): + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +class ImageContent(WireModel): + """An image provided to or from an LLM.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: str + """ + The base64-encoded image data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ + type: Literal["image"] + + +class InitializeResult(WireModel): + """After receiving an initialize request from the client, the server sends this response.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + capabilities: ServerCapabilities + instructions: str | None = None + """ + Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought + of like a "hint" to the model. For example, this information MAY be added to the system prompt. + """ + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The version of the Model Context Protocol that the server wants to use. This may not match the version that the + client requested. If the client cannot support this version, it MUST disconnect. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class ListRootsResult(WireModel): + """The client's response to a roots/list request from the server. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + roots: list[Root] + + +class Prompt(WireModel): + """A prompt or prompt template that the server offers.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ + description: str | None = None + """ + An optional description of what this prompt provides + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class ReadResourceResult(WireModel): + """The server's response to a resources/read request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + contents: list[TextResourceContents | BlobResourceContents] + + +class Resource(WireModel): + """A known resource that the server is capable of reading.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri: str + """ + The URI of this resource. + """ + + +class ResourceLink(WireModel): + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["resource_link"] + uri: str + """ + The URI of this resource. + """ + + +class ResourceTemplate(WireModel): + """A template description for resources available on the server.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type for all resources that match this template. This should only be included if all resources matching + this template have the same type. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri_template: Annotated[str, Field(alias="uriTemplate")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +class TextContent(WireModel): + """Text provided to or from an LLM.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + text: str + """ + The text content of the message. + """ + type: Literal["text"] + + +class Tool(WireModel): + """Definition for a tool the client can call.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: ToolAnnotations | None = None + """ + Optional additional tool information. + + Display name precedence order is: title, annotations.title, then name. + """ + description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a + "hint" to the model. + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + """ + An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class CreateMessageResult(WireModel): + """The client's response to a sampling/create_message request from the server. The client should inform the user + before returning the sampled message, to allow them to inspect the response (human in the loop) and decide + whether to allow the server to see it. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + content: TextContent | ImageContent | AudioContent + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + The reason why sampling stopped, if known. + """ + + +class ListPromptsResult(WireModel): + """The server's response to a prompts/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + prompts: list[Prompt] + + +class ListResourceTemplatesResult(WireModel): + """The server's response to a resources/templates/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + + +class ListResourcesResult(WireModel): + """The server's response to a resources/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resources: list[Resource] + + +class ListToolsResult(WireModel): + """The server's response to a tools/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tools: list[Tool] + + +class PromptMessage(WireModel): + """Describes a message returned as part of a prompt. + + This is similar to `SamplingMessage`, but also supports the embedding of + resources from the MCP server. + """ + + content: ContentBlock + role: Role + + +class SamplingMessage(WireModel): + """Describes a message issued to or received from an LLM API.""" + + content: TextContent | ImageContent | AudioContent + role: Role + + +class CallToolResult(WireModel): + """The server's response to a tool call.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + content: list[ContentBlock] + """ + A list of content objects that represent the unstructured result of the tool call. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ + structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None + """ + An optional JSON object that represents the structured result of the tool call. + """ + + +class CreateMessageRequestParams(WireModel): + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The + client MAY ignore this request. + """ + max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. + """ + messages: list[SamplingMessage] + metadata: dict[str, Any] | None = None + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ + temperature: float | None = None + + +class CreateMessageRequest(WireModel): + """A request from the server to sample an LLM via the client. The client has full discretion over which model to + select. The client should also inform the user before beginning sampling, to allow them to inspect the request + (human in the loop) and decide whether to approve it. + """ + + method: Literal["sampling/createMessage"] + params: CreateMessageRequestParams + + +class GetPromptResult(WireModel): + """The server's response to a prompts/get request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + description: str | None = None + """ + An optional description for the prompt. + """ + messages: list[PromptMessage] + + +# --- Aliases new or changed in 2025-06-18 --- +# (defined last: an alias right-hand side evaluates its referents at import) + +PrimitiveSchemaDefinition: TypeAlias = StringSchema | NumberSchema | BooleanSchema | EnumSchema + +ClientNotification: TypeAlias = ( + CancelledNotification | InitializedNotification | ProgressNotification | RootsListChangedNotification +) + +ClientRequest: TypeAlias = ( + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | SetLevelRequest + | CompleteRequest +) + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError + +ClientResult: TypeAlias = Result | CreateMessageResult | ListRootsResult | ElicitResult + +ServerRequest: TypeAlias = PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) + +RequestedSchema.model_rebuild() +PromptMessage.model_rebuild() +CallToolResult.model_rebuild() diff --git a/src/mcp/types/v2025_11_25/__init__.py b/src/mcp/types/v2025_11_25/__init__.py new file mode 100644 index 0000000000..519e8b1507 --- /dev/null +++ b/src/mcp/types/v2025_11_25/__init__.py @@ -0,0 +1,2504 @@ +"""Wire-shape models for MCP protocol version 2025-11-25 — not user-facing API. + +Defines only what this revision added or changed relative to 2025-06-18; +everything else is imported from the version module that last defined it, so +every import line names the module where a model is defined. +``REMOVED_FROM_PREVIOUS_VERSION`` lists the names 2025-06-18 defined that +this revision dropped. + +Consumed by ``mcp.types.wire``: ``serialize_for`` re-validates each outbound +monolith dump through the negotiated version's models, importing the version +module lazily on first boundary use (never at ``import mcp.types``). + +Initially generated from the pinned 2025-11-25 schema (spec commit +6d441518de) with datamodel-code-generator 0.57.0 plus a +mechanical delta split, then hand-validated against the pinned schema. +Maintained as ordinary source: the effective surface is asserted equal to the +pinned schema by ``tests/types/test_version_surfaces.py``, so a drifting edit +fails CI. + +The models are deliberately closed (``extra="ignore"``) even where the schema +declares an object open to extra fields — see ``mcp.types._wire_base`` for +the rationale. The classes kept open are commented in place. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Final, Literal, TypeAlias + +from pydantic import ConfigDict, Field + +from mcp.types._wire_base import WireModel + +# Unchanged since 2024-11-05: +from mcp.types.v2024_11_05 import ( + Argument, + CompleteResult, + Cursor, + EmptyResult, + Error, + LoggingLevel, + Meta, + ModelHint, + ModelPreferences, + NotificationParams, + PaginatedResult, + ProgressToken, + Prompts, + RequestId, + RequestParams, + Resources, + Result, + Role, + Roots, +) + +# Unchanged since 2025-03-26: +from mcp.types.v2025_03_26 import ( + ToolAnnotations, +) + +# Unchanged since 2025-06-18: +from mcp.types.v2025_06_18 import ( + Annotations, + AudioContent, + BaseMetadata, + BlobResourceContents, + BooleanSchema, + Context, + EmbeddedResource, + ImageContent, + ListRootsResult, + PromptArgument, + PromptReference, + ReadResourceResult, + ResourceContents, + ResourceTemplateReference, + Root, + TextContent, + TextResourceContents, +) + +REMOVED_FROM_PREVIOUS_VERSION: Final[frozenset[str]] = frozenset( + { + "JSONRPCError", + } +) + +__all__ = [ + "Annotations", + "AudioContent", + "BaseMetadata", + "BlobResourceContents", + "BooleanSchema", + "CallToolRequest", + "CallToolRequestParams", + "CallToolResult", + "CancelTaskRequest", + "CancelTaskResult", + "CancelledNotification", + "CancelledNotificationParams", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteRequestParams", + "CompleteResult", + "ContentBlock", + "CreateMessageRequest", + "CreateMessageRequestParams", + "CreateMessageResult", + "CreateTaskResult", + "Cursor", + "ElicitRequest", + "ElicitRequestFormParams", + "ElicitRequestParams", + "ElicitRequestURLParams", + "ElicitResult", + "ElicitationCompleteNotification", + "EmbeddedResource", + "EmptyResult", + "EnumSchema", + "Error", + "GetPromptRequest", + "GetPromptRequestParams", + "GetPromptResult", + "GetTaskPayloadRequest", + "GetTaskPayloadResult", + "GetTaskRequest", + "GetTaskResult", + "Icon", + "Icons", + "ImageContent", + "Implementation", + "InitializeRequest", + "InitializeRequestParams", + "InitializeResult", + "InitializedNotification", + "JSONRPCErrorResponse", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "JSONRPCResultResponse", + "LegacyTitledEnumSchema", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListTasksRequest", + "ListTasksResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "LoggingMessageNotificationParams", + "ModelHint", + "ModelPreferences", + "MultiSelectEnumSchema", + "Notification", + "NotificationParams", + "NumberSchema", + "PaginatedRequest", + "PaginatedRequestParams", + "PaginatedResult", + "PingRequest", + "PrimitiveSchemaDefinition", + "ProgressNotification", + "ProgressNotificationParams", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceRequestParams", + "ReadResourceResult", + "RelatedTaskMetadata", + "Request", + "RequestId", + "RequestParams", + "Resource", + "ResourceContents", + "ResourceLink", + "ResourceListChangedNotification", + "ResourceRequestParams", + "ResourceTemplate", + "ResourceTemplateReference", + "ResourceUpdatedNotification", + "ResourceUpdatedNotificationParams", + "Result", + "Role", + "Root", + "RootsListChangedNotification", + "SamplingMessage", + "SamplingMessageContentBlock", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "SetLevelRequestParams", + "SingleSelectEnumSchema", + "StringSchema", + "SubscribeRequest", + "SubscribeRequestParams", + "Task", + "TaskAugmentedRequestParams", + "TaskMetadata", + "TaskStatus", + "TaskStatusNotification", + "TaskStatusNotificationParams", + "TextContent", + "TextResourceContents", + "TitledMultiSelectEnumSchema", + "TitledSingleSelectEnumSchema", + "Tool", + "ToolAnnotations", + "ToolChoice", + "ToolExecution", + "ToolListChangedNotification", + "ToolResultContent", + "ToolUseContent", + "URLElicitationRequiredError", + "UnsubscribeRequest", + "UnsubscribeRequestParams", + "UntitledMultiSelectEnumSchema", + "UntitledSingleSelectEnumSchema", +] + +# --- New in 2025-11-25 --- + + +class CancelTaskRequestParams(WireModel): + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier to cancel. + """ + + +class Elicitation(WireModel): + """Present if the client supports elicitation from the server.""" + + form: dict[str, Any] | None = None + url: dict[str, Any] | None = None + + +class Sampling(WireModel): + """Present if the client supports sampling from an LLM.""" + + context: dict[str, Any] | None = None + """ + Whether the client supports context inclusion via includeContext parameter. + If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). + """ + tools: dict[str, Any] | None = None + """ + Whether the client supports tool use via tools and toolChoice parameters. + """ + + +class Elicitation1(WireModel): + """Task support for elicitation-related requests.""" + + create: dict[str, Any] | None = None + """ + Whether the client supports task-augmented elicitation/create requests. + """ + + +class Sampling1(WireModel): + """Task support for sampling-related requests.""" + + create_message: Annotated[dict[str, Any] | None, Field(alias="createMessage")] = None + """ + Whether the client supports task-augmented sampling/createMessage requests. + """ + + +class Requests(WireModel): + """Specifies which request types can be augmented with tasks.""" + + elicitation: Elicitation1 | None = None + """ + Task support for elicitation-related requests. + """ + sampling: Sampling1 | None = None + """ + Task support for sampling-related requests. + """ + + +class Tasks(WireModel): + """Present if the client supports task-augmented requests.""" + + cancel: dict[str, Any] | None = None + """ + Whether this client supports tasks/cancel. + """ + list: dict[str, Any] | None = None + """ + Whether this client supports tasks/list. + """ + requests: Requests | None = None + """ + Specifies which request types can be augmented with tasks. + """ + + +class ElicitationCompleteNotificationParams(WireModel): + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ + The ID of the elicitation that completed. + """ + + +class ElicitationCompleteNotification(WireModel): + """An optional notification from the server to the client, informing it of a completion of a out-of-band + elicitation request. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/elicitation/complete"] + params: ElicitationCompleteNotificationParams + + +class GetTaskPayloadRequestParams(WireModel): + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier to retrieve results for. + """ + + +class GetTaskPayloadResult(WireModel): + """The response to a tasks/result request. + The structure matches the result type of the original request. + For example, a tools/call task would return the CallToolResult structure. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + + +class GetTaskRequestParams(WireModel): + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier to query. + """ + + +class Icon(WireModel): + """An optionally-sized icon that can be displayed in a user interface.""" + + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + Optional MIME type override if the source MIME type is missing or generic. + For example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`. + """ + sizes: list[str] | None = None + """ + Optional array of strings that specify sizes at which the icon can be used. + Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + + If not provided, the client should assume that the icon can be used at any size. + """ + src: str + """ + A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a + `data:` URI with Base64-encoded image data. + + Consumers SHOULD takes steps to ensure URLs serving icons are from the + same domain as the client/server or a trusted domain. + + Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain + executable JavaScript. + """ + theme: Literal["dark", "light"] | None = None + """ + Optional specifier for the theme this icon is designed for. `light` indicates + the icon is designed to be used with a light background, and `dark` indicates + the icon is designed to be used with a dark background. + + If not provided, the client should assume the icon can be used with any theme. + """ + + +class Icons(WireModel): + """Base interface to add `icons` property.""" + + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + + +class LegacyTitledEnumSchema(WireModel): + """Use TitledSingleSelectEnumSchema instead. + This interface will be removed in a future version. + """ + + default: str | None = None + description: str | None = None + enum: list[str] + enum_names: Annotated[list[str] | None, Field(alias="enumNames")] = None + """ + (Legacy) Display names for enum values. + Non-standard according to JSON schema 2020-12. + """ + title: str | None = None + type: Literal["string"] + + +class RelatedTaskMetadata(WireModel): + """Metadata for associating messages with a task. + Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. + """ + + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier this message is associated with. + """ + + +class ResourceRequestParams(WireModel): + """Common parameters when working with resources.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class Requests1(WireModel): + """Specifies which request types can be augmented with tasks.""" + + tools: Tools | None = None + """ + Task support for tool-related requests. + """ + + +class Tasks1(WireModel): + """Present if the server supports task-augmented requests.""" + + cancel: dict[str, Any] | None = None + """ + Whether this server supports tasks/cancel. + """ + list: dict[str, Any] | None = None + """ + Whether this server supports tasks/list. + """ + requests: Requests1 | None = None + """ + Specifies which request types can be augmented with tasks. + """ + + +class Tools1(WireModel): + """Present if the server offers any tools to call.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the tool list. + """ + + +class TaskMetadata(WireModel): + """Metadata for augmenting a request with task execution. + Include this in the `task` field of the request parameters. + """ + + ttl: int | None = None + """ + Requested duration in milliseconds to retain task from creation. + """ + + +class AnyOfItem(WireModel): + const: str + """ + The constant enum value. + """ + title: str + """ + Display title for this option. + """ + + +class Items(WireModel): + """Schema for array items with enum options and display labels.""" + + any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] + """ + Array of enum options with values and display labels. + """ + + +class TitledMultiSelectEnumSchema(WireModel): + """Schema for multiple-selection enumeration with display titles for each option.""" + + default: list[str] | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + items: Items + """ + Schema for array items with enum options and display labels. + """ + max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ + min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["array"] + + +class OneOfItem(WireModel): + const: str + """ + The enum value. + """ + title: str + """ + Display label for this option. + """ + + +class TitledSingleSelectEnumSchema(WireModel): + """Schema for single-selection enumeration with display titles for each option.""" + + default: str | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + one_of: Annotated[list[OneOfItem], Field(alias="oneOf")] + """ + Array of enum options with values and display labels. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["string"] + + +class ToolChoice(WireModel): + """Controls tool selection behavior for sampling requests.""" + + mode: Literal["auto", "none", "required"] | None = None + """ + Controls the tool use ability of the model: + - "auto": Model decides whether to use tools (default) + - "required": Model MUST use at least one tool before completing + - "none": Model MUST NOT use any tools + """ + + +class ToolExecution(WireModel): + """Execution-related properties for a tool.""" + + task_support: Annotated[Literal["forbidden", "optional", "required"] | None, Field(alias="taskSupport")] = None + """ + Indicates whether this tool supports task-augmented execution. + This allows clients to handle long-running operations through polling + the task system. + + - "forbidden": Tool does not support task-augmented execution (default when absent) + - "optional": Tool may support task-augmented execution + - "required": Tool requires task-augmented execution + + Default: "forbidden" + """ + + +class ToolUseContent(WireModel): + """A request from the assistant to call a tool.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool use. Clients SHOULD preserve this field when + including tool uses in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + id: str + """ + A unique identifier for this tool use. + + This ID is used to match tool results to their corresponding tool uses. + """ + input: dict[str, Any] + """ + The arguments to pass to the tool, conforming to the tool's input schema. + """ + name: str + """ + The name of the tool to call. + """ + type: Literal["tool_use"] + + +class Items1(WireModel): + """Schema for the array items.""" + + enum: list[str] + """ + Array of enum values to choose from. + """ + type: Literal["string"] + + +class UntitledMultiSelectEnumSchema(WireModel): + """Schema for multiple-selection enumeration without display titles for options.""" + + default: list[str] | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + items: Items1 + """ + Schema for the array items. + """ + max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ + min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["array"] + + +class UntitledSingleSelectEnumSchema(WireModel): + """Schema for single-selection enumeration without display titles for options.""" + + default: str | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + enum: list[str] + """ + Array of enum values to choose from. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["string"] + + +class CancelTaskRequest(WireModel): + """A request to cancel a task.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/cancel"] + params: CancelTaskRequestParams + + +class ElicitRequestURLParams(WireModel): + """The parameters for a request to elicit information from the user via a URL in the client.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ + The ID of the elicitation, which must be unique within the context of the server. + The client MUST treat this ID as an opaque value. + """ + message: str + """ + The message to present to the user explaining why the interaction is needed. + """ + mode: Literal["url"] + """ + The elicitation mode. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + url: str + """ + The URL that the user should navigate to. + """ + + +class GetTaskPayloadRequest(WireModel): + """A request to retrieve the result of a completed task.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/result"] + params: GetTaskPayloadRequestParams + + +class GetTaskRequest(WireModel): + """A request to retrieve the state of a task.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/get"] + params: GetTaskRequestParams + + +class JSONRPCErrorResponse(WireModel): + """A response to a request that indicates an error occurred.""" + + error: Error + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class JSONRPCResultResponse(WireModel): + """A successful (non-error) response to a request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class Task(WireModel): + """Data associated with a task.""" + + created_at: Annotated[str, Field(alias="createdAt")] + """ + ISO 8601 timestamp when the task was created. + """ + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + """ + ISO 8601 timestamp when the task was last updated. + """ + poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None + """ + Suggested polling interval in milliseconds. + """ + status: TaskStatus + """ + Current task state. + """ + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier. + """ + ttl: int | None + """ + Actual retention duration from creation in milliseconds, null for unlimited. + """ + + +class TaskAugmentedRequestParams(WireModel): + """Common params for any task-augmented request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + + +class TaskStatusNotificationParams(WireModel): + """Parameters for a `notifications/tasks/status` notification.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + created_at: Annotated[str, Field(alias="createdAt")] + """ + ISO 8601 timestamp when the task was created. + """ + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + """ + ISO 8601 timestamp when the task was last updated. + """ + poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None + """ + Suggested polling interval in milliseconds. + """ + status: TaskStatus + """ + Current task state. + """ + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier. + """ + ttl: int | None + """ + Actual retention duration from creation in milliseconds, null for unlimited. + """ + + +class Data(WireModel): + """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + + elicitations: list[ElicitRequestURLParams] + + +class Error1(WireModel): + code: Literal[-32042] + """ + The error type that occurred. + """ + data: Data + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class URLElicitationRequiredError(WireModel): + """An error response that indicates that the server requires the client to provide additional information via an + elicitation request. + """ + + error: Error1 + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class CancelTaskResult(WireModel): + """The response to a tasks/cancel request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + created_at: Annotated[str, Field(alias="createdAt")] + """ + ISO 8601 timestamp when the task was created. + """ + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + """ + ISO 8601 timestamp when the task was last updated. + """ + poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None + """ + Suggested polling interval in milliseconds. + """ + status: TaskStatus + """ + Current task state. + """ + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier. + """ + ttl: int | None + """ + Actual retention duration from creation in milliseconds, null for unlimited. + """ + + +class CreateTaskResult(WireModel): + """A response to a task-augmented request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + task: Task + + +class ElicitRequestFormParams(WireModel): + """The parameters for a request to elicit non-sensitive information from the user via a form in the client.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + message: str + """ + The message to present to the user describing what information is being requested. + """ + mode: Literal["form"] = "form" + """ + The elicitation mode. + """ + requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + """ + A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + + +class GetTaskResult(WireModel): + """The response to a tasks/get request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + created_at: Annotated[str, Field(alias="createdAt")] + """ + ISO 8601 timestamp when the task was created. + """ + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + """ + ISO 8601 timestamp when the task was last updated. + """ + poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None + """ + Suggested polling interval in milliseconds. + """ + status: TaskStatus + """ + Current task state. + """ + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier. + """ + ttl: int | None + """ + Actual retention duration from creation in milliseconds, null for unlimited. + """ + + +class ListTasksRequest(WireModel): + """A request to retrieve a list of tasks.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/list"] + params: PaginatedRequestParams | None = None + + +class ListTasksResult(WireModel): + """The response to a tasks/list request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tasks: list[Task] + + +class TaskStatusNotification(WireModel): + """An optional notification from the receiver to the requestor, informing them that a task's status has changed. + Receivers are not required to send these notifications. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/tasks/status"] + params: TaskStatusNotificationParams + + +class ToolResultContent(WireModel): + """The result of a tool use, provided by the user back to the assistant.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool result. Clients SHOULD preserve this field when + including tool results in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + content: list[ContentBlock] + """ + The unstructured result content of the tool use. + + This has the same format as CallToolResult.content and can include text, images, + audio, resource links, and embedded resources. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool use resulted in an error. + + If true, the content typically describes the error that occurred. + Default: false + """ + structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None + """ + An optional structured result object. + + If the tool defined an outputSchema, this SHOULD conform to that schema. + """ + tool_use_id: Annotated[str, Field(alias="toolUseId")] + """ + The ID of the tool use this result corresponds to. + + This MUST match the ID from a previous ToolUseContent. + """ + type: Literal["tool_result"] + + +# --- Changed in 2025-11-25 --- + + +class ClientCapabilities(WireModel): + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any client can define its own, additional capabilities. + """ + + elicitation: Elicitation | None = None + """ + Present if the client supports elicitation from the server. + """ + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ + roots: Roots | None = None + """ + Present if the client supports listing roots. + """ + sampling: Sampling | None = None + """ + Present if the client supports sampling from an LLM. + """ + tasks: Tasks | None = None + """ + Present if the client supports task-augmented requests. + """ + + +class ElicitResult(WireModel): + """The client's response to an elicitation request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + action: Literal["accept", "cancel", "decline"] + """ + The user action in response to the elicitation. + - "accept": User submitted the form/confirmed the action + - "decline": User explicitly decline the action + - "cancel": User dismissed without making an explicit choice + """ + # Deliberate deviation from the pinned schema.json, which renders the + # value union's number arm as "integer" — its schema.ts source types form + # answers string | number | boolean | string[], so fractional answers are + # legal wire values. The float arm follows schema.ts; the generated + # oracle keeps the rendering verbatim and the surface test pins this + # annotation separately. + content: dict[str, list[str] | str | int | float | bool] | None = None + """ + The submitted form data, only present when action is "accept" and mode was "form". + Contains values matching the requested schema. + Omitted for out-of-band mode responses. + """ + + +class Implementation(WireModel): + """Describes the MCP implementation.""" + + description: str | None = None + """ + An optional human-readable description of what this implementation does. + + This can be used by clients or servers to provide context about their purpose + and capabilities. For example, a server might describe the types of resources + or tools it provides, while a client might describe its intended use case. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + version: str + website_url: Annotated[str | None, Field(alias="websiteUrl")] = None + """ + An optional URL of the website for this implementation. + """ + + +class JSONRPCNotification(WireModel): + """A notification which does not expect a response.""" + + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class LoggingMessageNotificationParams(WireModel): + """Parameters for a `notifications/message` notification.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ + level: LoggingLevel + """ + The severity of this log message. + """ + logger: str | None = None + """ + An optional name of the logger issuing this message. + """ + + +class Notification(WireModel): + method: str + params: dict[str, Any] | None = None + + +class NumberSchema(WireModel): + # Deliberate deviation from the pinned schema.json, which renders the + # default and the bounds as "integer" — schema.ts types them number + # (JSON Schema minimum/maximum/default are numbers; the schema describes + # number fields too). The float arms follow schema.ts; the generated + # oracle keeps the rendering verbatim and the surface test pins these + # annotations separately. + default: int | float | None = None + description: str | None = None + maximum: int | float | None = None + minimum: int | float | None = None + title: str | None = None + type: Literal["integer", "number"] + + +class PromptListChangedNotification(WireModel): + """An optional notification from the server to the client, informing it that the list of prompts it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/prompts/list_changed"] + params: NotificationParams | None = None + + +class ReadResourceRequestParams(WireModel): + """Parameters for a `resources/read` request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class Request(WireModel): + method: str + params: dict[str, Any] | None = None + + +class ResourceListChangedNotification(WireModel): + """An optional notification from the server to the client, informing it that the list of resources it can read + from has changed. This may be issued by servers without any previous subscription from the client. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/list_changed"] + params: NotificationParams | None = None + + +class ResourceUpdatedNotificationParams(WireModel): + """Parameters for a `notifications/resources/updated` notification.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: str + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually + subscribed to. + """ + + +class RootsListChangedNotification(WireModel): + """A notification from the client to the server, informing it that the list of roots has changed. + This notification should be sent whenever the client adds, removes, or modifies any root. + The server should then request an updated list of roots using the ListRootsRequest. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/roots/list_changed"] + params: NotificationParams | None = None + + +class Tools(WireModel): + """Task support for tool-related requests.""" + + call: dict[str, Any] | None = None + """ + Whether the server supports task-augmented tools/call requests. + """ + + +class ServerCapabilities(WireModel): + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any server can define its own, additional capabilities. + """ + + completions: dict[str, Any] | None = None + """ + Present if the server supports argument autocompletion suggestions. + """ + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ + logging: dict[str, Any] | None = None + """ + Present if the server supports sending log messages to the client. + """ + prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ + resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ + tasks: Tasks1 | None = None + """ + Present if the server supports task-augmented requests. + """ + tools: Tools1 | None = None + """ + Present if the server offers any tools to call. + """ + + +class SetLevelRequestParams(WireModel): + """Parameters for a `logging/setLevel` request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + level: LoggingLevel + """ + The level of logging that the client wants to receive from the server. The server should send all logs at this level + and higher (i.e., more severe) to the client as notifications/message. + """ + + +class StringSchema(WireModel): + default: str | None = None + description: str | None = None + format: Literal["date", "date-time", "email", "uri"] | None = None + max_length: Annotated[int | None, Field(alias="maxLength")] = None + min_length: Annotated[int | None, Field(alias="minLength")] = None + title: str | None = None + type: Literal["string"] + + +class SubscribeRequestParams(WireModel): + """Parameters for a `resources/subscribe` request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class InputSchema(WireModel): + """A JSON Schema object defining the expected parameters for the tool.""" + + # Stays open: schema keywords beyond the declared properties ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class OutputSchema(WireModel): + """An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + + Defaults to JSON Schema 2020-12 when no explicit $schema is provided. + Currently restricted to type: "object" at the root level. + """ + + # Stays open: schema keywords beyond the declared properties ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class ToolListChangedNotification(WireModel): + """An optional notification from the server to the client, informing it that the list of tools it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/tools/list_changed"] + params: NotificationParams | None = None + + +class UnsubscribeRequestParams(WireModel): + """Parameters for a `resources/unsubscribe` request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class CallToolRequestParams(WireModel): + """Parameters for a `tools/call` request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + arguments: dict[str, Any] | None = None + """ + Arguments to use for the tool call. + """ + name: str + """ + The name of the tool. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + + +class CancelledNotificationParams(WireModel): + """Parameters for a `notifications/cancelled` notification.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ + request_id: Annotated[RequestId | None, Field(alias="requestId")] = None + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + This MUST be provided for cancelling non-task requests. + This MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead). + """ + + +class CompleteRequestParams(WireModel): + """Parameters for a `completion/complete` request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + argument: Argument + """ + The argument's information + """ + context: Context | None = None + """ + Additional, optional context for completions + """ + ref: PromptReference | ResourceTemplateReference + + +class GetPromptRequestParams(WireModel): + """Parameters for a `prompts/get` request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ + name: str + """ + The name of the prompt or prompt template. + """ + + +class InitializeRequestParams(WireModel): + """Parameters for an `initialize` request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + capabilities: ClientCapabilities + client_info: Annotated[Implementation, Field(alias="clientInfo")] + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older + versions as well. + """ + + +class InitializeResult(WireModel): + """After receiving an initialize request from the client, the server sends this response.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + capabilities: ServerCapabilities + instructions: str | None = None + """ + Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought + of like a "hint" to the model. For example, this information MAY be added to the system prompt. + """ + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The version of the Model Context Protocol that the server wants to use. This may not match the version that the + client requested. If the client cannot support this version, it MUST disconnect. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class InitializedNotification(WireModel): + """This notification is sent from the client to the server after initialization has finished.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/initialized"] + params: NotificationParams | None = None + + +class JSONRPCRequest(WireModel): + """A request that expects a response.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class ListRootsRequest(WireModel): + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["roots/list"] + params: RequestParams | None = None + + +class LoggingMessageNotification(WireModel): + """JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been + sent from the client, the server MAY decide which messages to send automatically. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/message"] + params: LoggingMessageNotificationParams + + +class PaginatedRequestParams(WireModel): + """Common parameters for paginated requests.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class PingRequest(WireModel): + """A ping, issued by either the server or the client, to check that the other party is still alive. The receiver + must promptly respond, or else may be disconnected. + """ + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["ping"] + params: RequestParams | None = None + + +class ProgressNotificationParams(WireModel): + """Parameters for a `notifications/progress` notification.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + message: str | None = None + """ + An optional message describing the current progress. + """ + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """ + The progress token which was given in the initial request, used to associate this notification with the request that + is proceeding. + """ + total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ + + +class Prompt(WireModel): + """A prompt or prompt template that the server offers.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ + description: str | None = None + """ + An optional description of what this prompt provides + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class ReadResourceRequest(WireModel): + """Sent from the client to the server, to read a specific resource URI.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/read"] + params: ReadResourceRequestParams + + +class Resource(WireModel): + """A known resource that the server is capable of reading.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri: str + """ + The URI of this resource. + """ + + +class ResourceLink(WireModel): + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["resource_link"] + uri: str + """ + The URI of this resource. + """ + + +class ResourceTemplate(WireModel): + """A template description for resources available on the server.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type for all resources that match this template. This should only be included if all resources matching + this template have the same type. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri_template: Annotated[str, Field(alias="uriTemplate")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +class ResourceUpdatedNotification(WireModel): + """A notification from the server to the client, informing it that a resource has changed and may need to be read + again. This should only be sent if the client previously sent a resources/subscribe request. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/updated"] + params: ResourceUpdatedNotificationParams + + +class SetLevelRequest(WireModel): + """A request from the client to the server, to enable or adjust logging.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["logging/setLevel"] + params: SetLevelRequestParams + + +class SubscribeRequest(WireModel): + """Sent from the client to request resources/updated notifications from the server whenever a particular resource + changes. + """ + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/subscribe"] + params: SubscribeRequestParams + + +class Tool(WireModel): + """Definition for a tool the client can call.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: ToolAnnotations | None = None + """ + Optional additional tool information. + + Display name precedence order is: title, annotations.title, then name. + """ + description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a + "hint" to the model. + """ + execution: ToolExecution | None = None + """ + Execution-related properties for this tool. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + """ + An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + + Defaults to JSON Schema 2020-12 when no explicit $schema is provided. + Currently restricted to type: "object" at the root level. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class UnsubscribeRequest(WireModel): + """Sent from the client to request cancellation of resources/updated notifications from the server. This should + follow a previous resources/subscribe request. + """ + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/unsubscribe"] + params: UnsubscribeRequestParams + + +class CallToolRequest(WireModel): + """Used by the client to invoke a tool provided by the server.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/call"] + params: CallToolRequestParams + + +class CancelledNotification(WireModel): + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this + notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + + A client MUST NOT attempt to cancel its `initialize` request. + + For task cancellation, use the `tasks/cancel` request instead of this notification. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/cancelled"] + params: CancelledNotificationParams + + +class CompleteRequest(WireModel): + """A request from the client to the server, to ask for completion options.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["completion/complete"] + params: CompleteRequestParams + + +class RequestedSchema(WireModel): + """A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, PrimitiveSchemaDefinition] + required: list[str] | None = None + type: Literal["object"] + + +class GetPromptRequest(WireModel): + """Used by the client to get a prompt provided by the server.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/get"] + params: GetPromptRequestParams + + +class InitializeRequest(WireModel): + """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["initialize"] + params: InitializeRequestParams + + +class ListPromptsRequest(WireModel): + """Sent from the client to request a list of prompts and prompt templates the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/list"] + params: PaginatedRequestParams | None = None + + +class ListPromptsResult(WireModel): + """The server's response to a prompts/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + prompts: list[Prompt] + + +class ListResourceTemplatesRequest(WireModel): + """Sent from the client to request a list of resource templates the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/templates/list"] + params: PaginatedRequestParams | None = None + + +class ListResourceTemplatesResult(WireModel): + """The server's response to a resources/templates/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + + +class ListResourcesRequest(WireModel): + """Sent from the client to request a list of resources the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/list"] + params: PaginatedRequestParams | None = None + + +class ListResourcesResult(WireModel): + """The server's response to a resources/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resources: list[Resource] + + +class ListToolsRequest(WireModel): + """Sent from the client to request a list of tools the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/list"] + params: PaginatedRequestParams | None = None + + +class ListToolsResult(WireModel): + """The server's response to a tools/list request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tools: list[Tool] + + +class PaginatedRequest(WireModel): + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: PaginatedRequestParams | None = None + + +class ProgressNotification(WireModel): + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/progress"] + params: ProgressNotificationParams + + +class PromptMessage(WireModel): + """Describes a message returned as part of a prompt. + + This is similar to `SamplingMessage`, but also supports the embedding of + resources from the MCP server. + """ + + content: ContentBlock + role: Role + + +class CallToolResult(WireModel): + """The server's response to a tool call.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + content: list[ContentBlock] + """ + A list of content objects that represent the unstructured result of the tool call. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ + structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None + """ + An optional JSON object that represents the structured result of the tool call. + """ + + +class ElicitRequest(WireModel): + """A request from the server to elicit additional information from the user via the client.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["elicitation/create"] + params: ElicitRequestParams + + +class GetPromptResult(WireModel): + """The server's response to a prompts/get request from the client.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + description: str | None = None + """ + An optional description for the prompt. + """ + messages: list[PromptMessage] + + +class CreateMessageResult(WireModel): + """The client's response to a sampling/createMessage request from the server. + The client should inform the user before returning the sampled message, to allow them + to inspect the response (human in the loop) and decide whether to allow the server to see it. + """ + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + The reason why sampling stopped, if known. + + Standard values: + - "endTurn": Natural end of the assistant's turn + - "stopSequence": A stop sequence was encountered + - "maxTokens": Maximum token limit was reached + - "toolUse": The model wants to use one or more tools + + This field is an open string to allow for provider-specific stop reasons. + """ + + +class SamplingMessage(WireModel): + """Describes a message issued to or received from an LLM API.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + role: Role + + +class CreateMessageRequestParams(WireModel): + """Parameters for a `sampling/createMessage` request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + The client MAY ignore this request. + + Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if + the client + declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. + """ + max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. + """ + messages: list[SamplingMessage] + metadata: dict[str, Any] | None = None + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + temperature: float | None = None + tool_choice: Annotated[ToolChoice | None, Field(alias="toolChoice")] = None + """ + Controls how the model uses tools. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + Default is `{ mode: "auto" }`. + """ + tools: list[Tool] | None = None + """ + Tools that the model may use during generation. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + """ + + +class CreateMessageRequest(WireModel): + """A request from the server to sample an LLM via the client. The client has full discretion over which model to + select. The client should also inform the user before beginning sampling, to allow them to inspect the request + (human in the loop) and decide whether to approve it. + """ + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["sampling/createMessage"] + params: CreateMessageRequestParams + + +# --- Aliases new or changed in 2025-11-25 --- +# (defined last: an alias right-hand side evaluates its referents at import) + +TaskStatus: TypeAlias = Literal["cancelled", "completed", "failed", "input_required", "working"] + +EnumSchema: TypeAlias = ( + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + +MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + +SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + +ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + +JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse + +ClientNotification: TypeAlias = ( + CancelledNotification + | InitializedNotification + | ProgressNotification + | TaskStatusNotification + | RootsListChangedNotification +) + +ClientRequest: TypeAlias = ( + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | SetLevelRequest + | CompleteRequest +) + +SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | TaskStatusNotification + | LoggingMessageNotification + | ElicitationCompleteNotification +) + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CompleteResult +) + +ClientResult: TypeAlias = ( + Result + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CreateMessageResult + | ListRootsResult + | ElicitResult +) + +ServerRequest: TypeAlias = ( + PingRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | CreateMessageRequest + | ListRootsRequest + | ElicitRequest +) + +Requests1.model_rebuild() +Task.model_rebuild() +TaskStatusNotificationParams.model_rebuild() +CancelTaskResult.model_rebuild() +ElicitRequestFormParams.model_rebuild() +GetTaskResult.model_rebuild() +ListTasksRequest.model_rebuild() +ToolResultContent.model_rebuild() +RequestedSchema.model_rebuild() +PromptMessage.model_rebuild() +CallToolResult.model_rebuild() +ElicitRequest.model_rebuild() +CreateMessageResult.model_rebuild() +SamplingMessage.model_rebuild() diff --git a/src/mcp/types/v2026_07_28/__init__.py b/src/mcp/types/v2026_07_28/__init__.py new file mode 100644 index 0000000000..86fc8a7657 --- /dev/null +++ b/src/mcp/types/v2026_07_28/__init__.py @@ -0,0 +1,2482 @@ +"""Wire-shape models for MCP protocol version 2026-07-28 — not user-facing API. + +Defines only what this revision added or changed relative to 2025-11-25; +everything else is imported from the version module that last defined it, so +every import line names the module where a model is defined. +``REMOVED_FROM_PREVIOUS_VERSION`` lists the names 2025-11-25 defined that +this revision dropped. + +Consumed by ``mcp.types.wire``: ``serialize_for`` re-validates each outbound +monolith dump through the negotiated version's models, importing the version +module lazily on first boundary use (never at ``import mcp.types``). + +Initially generated from the pinned 2026-07-28 schema (spec commit +6d441518de) with datamodel-code-generator 0.57.0 plus a +mechanical delta split, then hand-validated against the pinned schema. +Maintained as ordinary source: the effective surface is asserted equal to the +pinned schema by ``tests/types/test_version_surfaces.py``, so a drifting edit +fails CI. + +The models are deliberately closed (``extra="ignore"``) even where the schema +declares an object open to extra fields — see ``mcp.types._wire_base`` for +the rationale. The classes kept open are commented in place. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Final, Literal, TypeAlias + +from pydantic import ConfigDict, Field +from typing_extensions import TypeAliasType + +from mcp.types._wire_base import OpenWireModel, WireModel + +# Unchanged since 2024-11-05: +from mcp.types.v2024_11_05 import ( + Argument, + Cursor, + Error, + LoggingLevel, + ModelHint, + ModelPreferences, + ProgressToken, + Prompts, + RequestId, + Resources, + Role, +) + +# Unchanged since 2025-03-26: +from mcp.types.v2025_03_26 import ( + ToolAnnotations, +) + +# Unchanged since 2025-06-18: +from mcp.types.v2025_06_18 import ( + Annotations, + BaseMetadata, + BooleanSchema, + Context, + PromptArgument, + PromptReference, + ResourceTemplateReference, +) + +# Unchanged since 2025-11-25: +from mcp.types.v2025_11_25 import ( + ElicitationCompleteNotification, + EnumSchema, + Icon, + Icons, + Implementation, + JSONRPCErrorResponse, + JSONRPCNotification, + JSONRPCRequest, + LegacyTitledEnumSchema, + MultiSelectEnumSchema, + Notification, + Request, + SingleSelectEnumSchema, + StringSchema, + TitledMultiSelectEnumSchema, + TitledSingleSelectEnumSchema, + ToolChoice, + UntitledMultiSelectEnumSchema, + UntitledSingleSelectEnumSchema, +) + +REMOVED_FROM_PREVIOUS_VERSION: Final[frozenset[str]] = frozenset( + { + "CancelTaskRequest", + "CancelTaskResult", + "CreateTaskResult", + "GetTaskPayloadRequest", + "GetTaskPayloadResult", + "GetTaskRequest", + "GetTaskResult", + "InitializeRequest", + "InitializeRequestParams", + "InitializeResult", + "InitializedNotification", + "ListTasksRequest", + "ListTasksResult", + "PingRequest", + "RelatedTaskMetadata", + "RootsListChangedNotification", + "ServerRequest", + "SetLevelRequest", + "SetLevelRequestParams", + "SubscribeRequest", + "SubscribeRequestParams", + "Task", + "TaskAugmentedRequestParams", + "TaskMetadata", + "TaskStatus", + "TaskStatusNotification", + "TaskStatusNotificationParams", + "ToolExecution", + "URLElicitationRequiredError", + "UnsubscribeRequest", + "UnsubscribeRequestParams", + } +) + +__all__ = [ + "Annotations", + "AudioContent", + "BaseMetadata", + "BlobResourceContents", + "BooleanSchema", + "CacheableResult", + "CallToolRequest", + "CallToolRequestParams", + "CallToolResult", + "CallToolResultResponse", + "CancelledNotification", + "CancelledNotificationParams", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteRequestParams", + "CompleteResult", + "CompleteResultResponse", + "ContentBlock", + "CreateMessageRequest", + "CreateMessageRequestParams", + "CreateMessageResult", + "Cursor", + "DiscoverRequest", + "DiscoverResult", + "DiscoverResultResponse", + "ElicitRequest", + "ElicitRequestFormParams", + "ElicitRequestParams", + "ElicitRequestURLParams", + "ElicitResult", + "ElicitationCompleteNotification", + "EmbeddedResource", + "EmptyResult", + "EnumSchema", + "Error", + "GetPromptRequest", + "GetPromptRequestParams", + "GetPromptResult", + "GetPromptResultResponse", + "Icon", + "Icons", + "ImageContent", + "Implementation", + "InputRequest", + "InputRequests", + "InputRequiredResult", + "InputResponse", + "InputResponseRequestParams", + "InputResponses", + "InternalError", + "InvalidParamsError", + "InvalidRequestError", + "JSONArray", + "JSONObject", + "JSONRPCErrorResponse", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "JSONRPCResultResponse", + "JSONValue", + "LegacyTitledEnumSchema", + "ListPromptsRequest", + "ListPromptsResult", + "ListPromptsResultResponse", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourceTemplatesResultResponse", + "ListResourcesRequest", + "ListResourcesResult", + "ListResourcesResultResponse", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "ListToolsResultResponse", + "LoggingLevel", + "LoggingMessageNotification", + "LoggingMessageNotificationParams", + "MetaObject", + "MethodNotFoundError", + "MissingRequiredClientCapabilityError", + "ModelHint", + "ModelPreferences", + "MultiSelectEnumSchema", + "Notification", + "NotificationParams", + "NumberSchema", + "PaginatedRequest", + "PaginatedRequestParams", + "PaginatedResult", + "ParseError", + "PrimitiveSchemaDefinition", + "ProgressNotification", + "ProgressNotificationParams", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceRequestParams", + "ReadResourceResult", + "ReadResourceResultResponse", + "Request", + "RequestId", + "RequestMetaObject", + "RequestParams", + "Resource", + "ResourceContents", + "ResourceLink", + "ResourceListChangedNotification", + "ResourceRequestParams", + "ResourceTemplate", + "ResourceTemplateReference", + "ResourceUpdatedNotification", + "ResourceUpdatedNotificationParams", + "Result", + "ResultType", + "Role", + "Root", + "SamplingMessage", + "SamplingMessageContentBlock", + "ServerCapabilities", + "ServerNotification", + "ServerResult", + "SingleSelectEnumSchema", + "StringSchema", + "SubscriptionFilter", + "SubscriptionsAcknowledgedNotification", + "SubscriptionsAcknowledgedNotificationParams", + "SubscriptionsListenRequest", + "SubscriptionsListenRequestParams", + "TextContent", + "TextResourceContents", + "TitledMultiSelectEnumSchema", + "TitledSingleSelectEnumSchema", + "Tool", + "ToolAnnotations", + "ToolChoice", + "ToolListChangedNotification", + "ToolResultContent", + "ToolUseContent", + "UnsupportedProtocolVersionError", + "UntitledMultiSelectEnumSchema", + "UntitledSingleSelectEnumSchema", +] + +# --- Recursive aliases new or changed in 2026-07-28 --- +# (defined first: their values are strings resolved only when a model is +# built, and static type checkers need the definition before its uses) + +# Deliberate deviation from the pinned schema.json, which renders JSONValue's +# primitive branch as ["string", "integer", "boolean"] — its schema.ts source +# defines all six JSON types (string | number | boolean | null | object | +# array), so the render is missing fractional numbers and null. This alias +# follows the schema.ts definition: capability values like {"ratio": 0.5} or +# nested nulls must survive revalidation. The generated oracle keeps the +# schema.json shape verbatim; the surface test pins this alias separately. +JSONValue = TypeAliasType("JSONValue", "JSONObject | list[JSONValue] | str | int | float | bool | None") + +JSONObject = TypeAliasType("JSONObject", dict[str, "JSONValue"]) + +# --- New in 2026-07-28 --- + + +class InternalError(WireModel): + """A JSON-RPC error indicating that an internal error occurred on the receiver. This error is returned when the + receiver encounters an unexpected condition that prevents it from fulfilling the request. + """ + + code: Literal[-32603] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class InvalidParamsError(WireModel): + """A JSON-RPC error indicating that the method parameters are invalid or malformed. + + In MCP, this error is returned in various contexts when request parameters fail validation: + + - **Tools**: Unknown tool name or invalid tool arguments + - **Prompts**: Unknown prompt name or missing required arguments + - **Pagination**: Invalid or expired cursor values + - **Logging**: Invalid log level + - **Elicitation**: Server requests an elicitation mode not declared in client capabilities + - **Sampling**: Missing tool result or tool results mixed with other content + """ + + code: Literal[-32602] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class InvalidRequestError(WireModel): + """A JSON-RPC error indicating that the request is not a valid request object. This error is returned when the + message structure does not conform to the JSON-RPC 2.0 specification requirements for a request (e.g., missing + required fields like `jsonrpc` or `method`, or using invalid types for these fields). + """ + + code: Literal[-32600] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class MetaObject(OpenWireModel): + """Represents the contents of a `_meta` field, which clients and servers use to attach additional metadata to their + interactions. + + Certain key names are reserved by MCP for protocol-level metadata; implementations MUST NOT make assumptions about + values at these keys. Additionally, specific schema definitions may reserve particular names for purpose-specific + metadata, as declared in those definitions. + + Valid keys have two segments: + + **Prefix:** + - Optional — if specified, MUST be a series of _labels_ separated by dots (`.`), followed by a slash (`/`). + - Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or + hyphens (`-`). + - Implementations SHOULD use reverse DNS notation (e.g., `com.example/` rather than `example.com/`). + - Any prefix where the second label is `modelcontextprotocol` or `mcp` is **reserved** for MCP use. For example: + `io.modelcontextprotocol/`, `dev.mcp/`, `org.modelcontextprotocol.api/`, and `com.mcp.tools/` are all reserved. + However, `com.example.mcp/` is NOT reserved, as the second label is `example`. + + **Name:** + - Unless empty, MUST start and end with an alphanumeric character (`[a-z0-9A-Z]`). + - Interior characters may be alphanumeric, hyphens (`-`), underscores (`_`), or dots (`.`). + """ + + +class MethodNotFoundError(WireModel): + """A JSON-RPC error indicating that the requested method does not exist or is not available. + + In MCP, a server returns this error when a client invokes a method the server does not implement — either a + genuinely unknown method, or one gated behind a server capability the server did not advertise (e.g., calling + `prompts/list` when the `prompts` capability was not advertised). + + A request that requires a client capability the client did not declare is signalled instead by + MissingRequiredClientCapabilityError (`-32003`). + """ + + code: Literal[-32601] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class ParseError(WireModel): + """A JSON-RPC error indicating that invalid JSON was received by the server. This error is returned when the + server cannot parse the JSON text of a message. + """ + + code: Literal[-32700] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class SubscriptionFilter(WireModel): + """The set of notification types a client may opt in to on a + SubscriptionsListenRequestsubscriptions/listen request. + + Each notification type is **opt-in**; the server **MUST NOT** send + notification types the client has not explicitly requested here. + """ + + # Stays open: filter contents are extensible on the wire. + model_config = ConfigDict( + extra="allow", + ) + prompts_list_changed: Annotated[bool | None, Field(alias="promptsListChanged")] = None + """ + If true, receive PromptListChangedNotificationnotifications/prompts/list_changed. + """ + resource_subscriptions: Annotated[list[str] | None, Field(alias="resourceSubscriptions")] = None + """ + Subscribe to ResourceUpdatedNotificationnotifications/resources/updated for these resource URIs. + Replaces the former `resources/subscribe` RPC. + """ + resources_list_changed: Annotated[bool | None, Field(alias="resourcesListChanged")] = None + """ + If true, receive ResourceListChangedNotificationnotifications/resources/list_changed. + """ + tools_list_changed: Annotated[bool | None, Field(alias="toolsListChanged")] = None + """ + If true, receive ToolListChangedNotificationnotifications/tools/list_changed. + """ + + +class SubscriptionsAcknowledgedNotificationParams(WireModel): + """Parameters for a SubscriptionsAcknowledgedNotificationnotifications/subscriptions/acknowledged notification.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + notifications: SubscriptionFilter + """ + The subset of requested notification types the server agreed to honor. + Only includes notification types the server actually supports; if the + client requested an unsupported type (e.g., `promptsListChanged` when + the server has no prompts), it is omitted from this set. + """ + + +class Data1(WireModel): + """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + + requested: str + """ + The protocol version that was requested by the client. + """ + supported: list[str] + """ + Protocol versions the server supports. The client should choose a + mutually supported version from this list and retry. + """ + + +class Error2(WireModel): + code: Literal[-32004] + """ + The error type that occurred. + """ + data: Data1 + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class UnsupportedProtocolVersionError(WireModel): + """Returned when the request's protocol version is unknown to the server or + unsupported (e.g., a known experimental or draft version the server has + chosen not to implement). For HTTP, the response status code MUST be + `400 Bad Request`. + """ + + error: Error2 + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class CacheableResult(WireModel): + """A result that supports a time-to-live (TTL) hint for client-side caching.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class CompleteResultResponse(WireModel): + """A successful response from the server for a CompleteRequestcompletion/complete request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: CompleteResult + + +class SubscriptionsAcknowledgedNotification(WireModel): + """Sent by the server as the first message on a + SubscriptionsListenRequestsubscriptions/listen stream to acknowledge + that the subscription has been established and to report which notification + types it agreed to honor. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/subscriptions/acknowledged"] + params: SubscriptionsAcknowledgedNotificationParams + + +class ListPromptsResultResponse(WireModel): + """A successful response from the server for a ListPromptsRequestprompts/list request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListPromptsResult + + +class ListResourceTemplatesResultResponse(WireModel): + """A successful response from the server for a ListResourceTemplatesRequestresources/templates/list request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourceTemplatesResult + + +class ListResourcesResultResponse(WireModel): + """A successful response from the server for a ListResourcesRequestresources/list request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourcesResult + + +class ListToolsResultResponse(WireModel): + """A successful response from the server for a ListToolsRequesttools/list request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListToolsResult + + +class CallToolResultResponse(WireModel): + """A successful response from the server for a CallToolRequesttools/call request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | CallToolResult + + +class DiscoverRequest(WireModel): + """A request from the client asking the server to advertise its supported + protocol versions, capabilities, and other metadata. Servers **MUST** + implement `server/discover`. Clients **MAY** call it but are not required + to — version negotiation can also happen inline via per-request `_meta`. + """ + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["server/discover"] + params: RequestParams + + +class DiscoverResult(WireModel): + """The result returned by the server for a DiscoverRequestserver/discover request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + capabilities: ServerCapabilities + """ + The capabilities of the server. + """ + instructions: str | None = None + """ + Natural-language guidance describing the server and its features. + + This can be used by clients to improve an LLM's understanding of + available tools (e.g., by including it in a system prompt). It should + focus on information that helps the model use the server effectively + and should not duplicate information already in tool descriptions. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + """ + Information about the server software implementation. + """ + supported_versions: Annotated[list[str], Field(alias="supportedVersions")] + """ + MCP Protocol Versions this server supports. The client should choose a + version from this list for use in subsequent requests. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class DiscoverResultResponse(WireModel): + """A successful response from the server for a DiscoverRequestserver/discover request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: DiscoverResult + + +class GetPromptResultResponse(WireModel): + """A successful response from the server for a GetPromptRequestprompts/get request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | GetPromptResult + + +class InputRequiredResult(WireModel): + """An InputRequiredResult sent by the server to indicate that additional input is needed + before the request can be completed. + + At least one of `inputRequests` or `requestState` MUST be present. + """ + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + input_requests: Annotated[InputRequests | None, Field(alias="inputRequests")] = None + request_state: Annotated[str | None, Field(alias="requestState")] = None + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +class InputResponseRequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + request_state: Annotated[str | None, Field(alias="requestState")] = None + + +class MissingRequiredClientCapabilityError(WireModel): + """Returned when processing a request requires a capability the client did not + declare in `clientCapabilities`. For HTTP, the response status code MUST be + `400 Bad Request`. + """ + + error: Error1 + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class ReadResourceResultResponse(WireModel): + """A successful response from the server for a ReadResourceRequestresources/read request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | ReadResourceResult + + +class RequestMetaObject(OpenWireModel): + """Extends MetaObject with additional request-specific fields. All key naming rules from `MetaObject` apply.""" + + io_modelcontextprotocol_client_capabilities: Annotated[ + ClientCapabilities, Field(alias="io.modelcontextprotocol/clientCapabilities") + ] + """ + The client's capabilities for this specific request. Required. + + Capabilities are declared per-request rather than once at initialization; + an empty object means the client supports no optional capabilities. + Servers MUST NOT infer capabilities from prior requests. + """ + io_modelcontextprotocol_client_info: Annotated[Implementation, Field(alias="io.modelcontextprotocol/clientInfo")] + """ + Identifies the client software making the request. Required. + + The Implementation schema requires `name` and `version`; other + fields are optional. + """ + io_modelcontextprotocol_log_level: Annotated[ + LoggingLevel | None, Field(alias="io.modelcontextprotocol/logLevel") + ] = None + """ + The desired log level for this request. Optional. + + If absent, the server MUST NOT send any LoggingMessageNotificationnotifications/message + notifications for this request. The client opts in to log messages by + explicitly setting a level. Replaces the former `logging/setLevel` RPC. + """ + io_modelcontextprotocol_protocol_version: Annotated[str, Field(alias="io.modelcontextprotocol/protocolVersion")] + """ + The MCP Protocol Version being used for this request. Required. + + For the HTTP transport, this value MUST match the `MCP-Protocol-Version` + header; otherwise the server MUST return a `400 Bad Request`. If the + server does not support the requested version, it MUST return an + UnsupportedProtocolVersionError. + """ + progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by + ProgressNotificationnotifications/progress). The value of this parameter is an opaque token that will be attached to + any subsequent notifications. The receiver is not obligated to provide these notifications. + """ + + +class SubscriptionsListenRequest(WireModel): + """Sent from the client to open a long-lived channel for receiving notifications + outside the context of a specific request. Replaces the previous HTTP GET + endpoint and ensures consistent behavior between HTTP and STDIO. + """ + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["subscriptions/listen"] + params: SubscriptionsListenRequestParams + + +class SubscriptionsListenRequestParams(WireModel): + """Parameters for a SubscriptionsListenRequestsubscriptions/listen request.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + notifications: SubscriptionFilter + """ + The notifications the client opts in to on this stream. The server + **MUST NOT** send notification types the client has not explicitly + requested. + """ + + +# --- Changed in 2026-07-28 --- + + +class Completion(WireModel): + has_more: Annotated[bool | None, Field(alias="hasMore")] = None + """ + Indicates whether there are additional completion options beyond those provided in the current response, even if the + exact total is unknown. + """ + total: int | None = None + """ + The total number of completion options available. This can exceed the number of values actually sent in the + response. + """ + values: Annotated[list[str], Field(max_length=100)] + """ + An array of completion values. Must not exceed 100 items. + """ + + +class ElicitRequestURLParams(WireModel): + """The parameters for a request to elicit information from the user via a URL in the client.""" + + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ + The ID of the elicitation, which must be unique within the context of the server. + The client MUST treat this ID as an opaque value. + """ + message: str + """ + The message to present to the user explaining why the interaction is needed. + """ + mode: Literal["url"] + """ + The elicitation mode. + """ + url: str + """ + The URL that the user should navigate to. + """ + + +class ElicitResult(WireModel): + """The result returned by the client for an ElicitRequestelicitation/create request.""" + + action: Literal["accept", "cancel", "decline"] + """ + The user action in response to the elicitation. + - `"accept"`: User submitted the form/confirmed the action + - `"decline"`: User explicitly declined the action + - `"cancel"`: User dismissed without making an explicit choice + """ + # Deliberate deviation from the pinned schema.json, which renders the + # value union's number arm as "integer" — its schema.ts source types form + # answers string | number | boolean | string[], so fractional answers are + # legal wire values (the same render artifact fixed for JSONValue above). + # The float arm follows schema.ts; the generated oracle keeps the + # rendering verbatim and the surface test pins this annotation separately. + content: dict[str, list[str] | str | int | float | bool] | None = None + """ + The submitted form data, only present when action is `"accept"` and mode was `"form"`. + Contains values matching the requested schema. + Omitted for out-of-band mode responses. + """ + + +class NotificationParams(WireModel): + """Common params for any notification.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + + +class NumberSchema(WireModel): + default: float | None = None + description: str | None = None + maximum: float | None = None + minimum: float | None = None + title: str | None = None + type: Literal["integer", "number"] + + +class PaginatedResult(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +class PromptListChangedNotification(WireModel): + """An optional notification from the server to the client, informing it that the list of prompts it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/prompts/list_changed"] + params: NotificationParams | None = None + + +class ResourceContents(WireModel): + """The contents of a specific resource or sub-resource.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: str + """ + The URI of this resource. + """ + + +class ResourceListChangedNotification(WireModel): + """An optional notification from the server to the client, informing it that the list of resources it can read + from has changed. This may be issued by servers without any previous subscription from the client. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/list_changed"] + params: NotificationParams | None = None + + +class ResourceUpdatedNotificationParams(WireModel): + """Parameters for a `notifications/resources/updated` notification.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + uri: str + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually + subscribed to. + """ + + +class Result(WireModel): + """Common result fields.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +class Root(WireModel): + """Represents a root directory or file that the server can operate on.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ + uri: str + """ + The URI identifying the root. This *must* start with `file://` for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ + + +class Tools(WireModel): + """Present if the server offers any tools to call.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the tool list. + """ + + +class TextResourceContents(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ + uri: str + """ + The URI of this resource. + """ + + +class InputSchema(WireModel): + """A JSON Schema object defining the expected parameters for the tool. + + Tool arguments are always JSON objects, so `type: "object"` is required at the root. + Beyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including + composition keywords (`oneOf`, `anyOf`, `allOf`, `not`), conditional keywords + (`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other + standard validation or annotation keywords. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ + + # Stays open: schema keywords beyond the declared properties ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + type: Literal["object"] + + +class OutputSchema(WireModel): + """An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ + + # Stays open: schema keywords beyond the declared properties ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + + +class ToolListChangedNotification(WireModel): + """An optional notification from the server to the client, informing it that the list of tools it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/tools/list_changed"] + params: NotificationParams | None = None + + +class ToolUseContent(WireModel): + """A request from the assistant to call a tool.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool use. Clients SHOULD preserve this field when + including tool uses in subsequent sampling requests to enable caching optimizations. + """ + id: str + """ + A unique identifier for this tool use. + + This ID is used to match tool results to their corresponding tool uses. + """ + input: dict[str, Any] + """ + The arguments to pass to the tool, conforming to the tool's input schema. + """ + name: str + """ + The name of the tool to call. + """ + type: Literal["tool_use"] + + +class AudioContent(WireModel): + """Audio provided to or from an LLM.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: str + """ + The base64-encoded audio data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the audio. Different providers may support different audio types. + """ + type: Literal["audio"] + + +class BlobResourceContents(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + blob: str + """ + A base64-encoded string representing the binary data of the item. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: str + """ + The URI of this resource. + """ + + +class CancelledNotificationParams(WireModel): + """Parameters for a `notifications/cancelled` notification.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ + request_id: Annotated[RequestId | None, Field(alias="requestId")] = None + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + """ + + +class CompleteResult(WireModel): + """The result returned by the server for a CompleteRequestcompletion/complete request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + completion: Completion + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +class EmbeddedResource(WireModel): + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +class ImageContent(WireModel): + """An image provided to or from an LLM.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: str + """ + The base64-encoded image data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ + type: Literal["image"] + + +class JSONRPCResultResponse(WireModel): + """A successful (non-error) response to a request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class ListRootsResult(WireModel): + """The result returned by the client for a ListRootsRequestroots/list request. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ + + roots: list[Root] + + +class LoggingMessageNotificationParams(WireModel): + """Parameters for a `notifications/message` notification.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ + level: LoggingLevel + """ + The severity of this log message. + """ + logger: str | None = None + """ + An optional name of the logger issuing this message. + """ + + +class ProgressNotificationParams(WireModel): + """Parameters for a ProgressNotificationnotifications/progress notification.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + message: str | None = None + """ + An optional message describing the current progress. + """ + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """ + The progress token which was given in the initial request, used to associate this notification with the request that + is proceeding. + """ + total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ + + +class Prompt(WireModel): + """A prompt or prompt template that the server offers.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ + description: str | None = None + """ + An optional description of what this prompt provides + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class ReadResourceResult(WireModel): + """The result returned by the server for a ReadResourceRequestresources/read request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + contents: list[TextResourceContents | BlobResourceContents] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class Resource(WireModel): + """A known resource that the server is capable of reading.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri: str + """ + The URI of this resource. + """ + + +class ResourceLink(WireModel): + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of + ListResourcesRequestresources/list requests. + """ + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["resource_link"] + uri: str + """ + The URI of this resource. + """ + + +class ResourceTemplate(WireModel): + """A template description for resources available on the server.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type for all resources that match this template. This should only be included if all resources matching + this template have the same type. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri_template: Annotated[str, Field(alias="uriTemplate")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +class ResourceUpdatedNotification(WireModel): + """A notification from the server to the client, informing it that a resource has changed and may need to be read + again. This is only sent for resources the client opted in to via the `resourceSubscriptions` field of a + SubscriptionsListenRequestsubscriptions/listen request. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/updated"] + params: ResourceUpdatedNotificationParams + + +class TextContent(WireModel): + """Text provided to or from an LLM.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + text: str + """ + The text content of the message. + """ + type: Literal["text"] + + +class Tool(WireModel): + """Definition for a tool the client can call.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: ToolAnnotations | None = None + """ + Optional additional tool information. + + Display name precedence order is: `title`, `annotations.title`, then `name`. + """ + description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a + "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + + Tool arguments are always JSON objects, so `type: "object"` is required at the root. + Beyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including + composition keywords (`oneOf`, `anyOf`, `allOf`, `not`), conditional keywords + (`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other + standard validation or annotation keywords. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ + output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + """ + An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class CancelledNotification(WireModel): + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this + notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/cancelled"] + params: CancelledNotificationParams + + +class RequestedSchema(WireModel): + """A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, PrimitiveSchemaDefinition] + required: list[str] | None = None + type: Literal["object"] + + +class ElicitRequestFormParams(WireModel): + """The parameters for a request to elicit non-sensitive information from the user via a form in the client.""" + + message: str + """ + The message to present to the user describing what information is being requested. + """ + mode: Literal["form"] = "form" + """ + The elicitation mode. + """ + requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + """ + A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + +class ListPromptsResult(WireModel): + """The result returned by the server for a ListPromptsRequestprompts/list request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + prompts: list[Prompt] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class ListResourceTemplatesResult(WireModel): + """The result returned by the server for a ListResourceTemplatesRequestresources/templates/list request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class ListResourcesResult(WireModel): + """The result returned by the server for a ListResourcesRequestresources/list request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resources: list[Resource] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class ListToolsResult(WireModel): + """The result returned by the server for a ListToolsRequesttools/list request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + tools: list[Tool] + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class LoggingMessageNotification(WireModel): + """JSONRPCNotification of a log message passed from server to client. The client opts in by setting + `"io.modelcontextprotocol/logLevel"` in a request's `_meta`. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/message"] + params: LoggingMessageNotificationParams + + +class ProgressNotification(WireModel): + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/progress"] + params: ProgressNotificationParams + + +class PromptMessage(WireModel): + """Describes a message returned as part of a prompt. + + This is similar to SamplingMessage, but also supports the embedding of + resources from the MCP server. + """ + + content: ContentBlock + role: Role + + +class ToolResultContent(WireModel): + """The result of a tool use, provided by the user back to the assistant.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool result. Clients SHOULD preserve this field when + including tool results in subsequent sampling requests to enable caching optimizations. + """ + content: list[ContentBlock] + """ + The unstructured result content of the tool use. + + This has the same format as CallToolResult.content and can include text, images, + audio, resource links, and embedded resources. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool use resulted in an error. + + If true, the content typically describes the error that occurred. + Default: false + """ + structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None + """ + An optional structured result value. + + This can be any JSON value (object, array, string, number, boolean, or null). + If the tool defined an Tool.outputSchema, this SHOULD conform to that schema. + """ + tool_use_id: Annotated[str, Field(alias="toolUseId")] + """ + The ID of the tool use this result corresponds to. + + This MUST match the ID from a previous ToolUseContent. + """ + type: Literal["tool_result"] + + +class CallToolResult(WireModel): + """The result returned by the server for a CallToolRequesttools/call request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: list[ContentBlock] + """ + A list of content objects that represent the unstructured result of the tool call. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None + """ + An optional JSON value that represents the structured result of the tool call. + + This can be any JSON value (object, array, string, number, boolean, or null) + that conforms to the tool's outputSchema if one is defined. + """ + + +class ElicitRequest(WireModel): + """A request from the server to elicit additional information from the user via the client.""" + + method: Literal["elicitation/create"] + params: ElicitRequestParams + + +class GetPromptResult(WireModel): + """The result returned by the server for a GetPromptRequestprompts/get request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + description: str | None = None + """ + An optional description for the prompt. + """ + messages: list[PromptMessage] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +class CreateMessageResult(WireModel): + """The result returned by the client for a CreateMessageRequestsampling/createMessage request. + The client should inform the user before returning the sampled message, to allow them + to inspect the response (human in the loop) and decide whether to allow the server to see it. + """ + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + The reason why sampling stopped, if known. + + Standard values: + - `"endTurn"`: Natural end of the assistant's turn + - `"stopSequence"`: A stop sequence was encountered + - `"maxTokens"`: Maximum token limit was reached + - `"toolUse"`: The model wants to use one or more tools + + This field is an open string to allow for provider-specific stop reasons. + """ + + +class SamplingMessage(WireModel): + """Describes a message issued to or received from an LLM API.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + role: Role + + +class CallToolRequest(WireModel): + """Used by the client to invoke a tool provided by the server.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/call"] + params: CallToolRequestParams + + +class CallToolRequestParams(WireModel): + """Parameters for a `tools/call` request.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + arguments: dict[str, Any] | None = None + """ + Arguments to use for the tool call. + """ + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + name: str + """ + The name of the tool. + """ + request_state: Annotated[str | None, Field(alias="requestState")] = None + + +class Elicitation(WireModel): + """Present if the client supports elicitation from the server.""" + + form: JSONObject | None = None + url: JSONObject | None = None + + +class Sampling(WireModel): + """Present if the client supports sampling from an LLM.""" + + context: JSONObject | None = None + """ + Whether the client supports context inclusion via `includeContext` parameter. + If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). + """ + tools: JSONObject | None = None + """ + Whether the client supports tool use via `tools` and `toolChoice` parameters. + """ + + +class ClientCapabilities(WireModel): + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any client can define its own, additional capabilities. + """ + + elicitation: Elicitation | None = None + """ + Present if the client supports elicitation from the server. + """ + experimental: dict[str, JSONObject] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ + extensions: dict[str, JSONObject] | None = None + """ + Optional MCP extensions that the client supports. Keys are extension identifiers + (e.g., "io.modelcontextprotocol/oauth-client-credentials"), and values are + per-extension settings objects. An empty object indicates support with no settings. + """ + roots: dict[str, Any] | None = None + """ + Present if the client supports listing roots. + """ + sampling: Sampling | None = None + """ + Present if the client supports sampling from an LLM. + """ + + +class CompleteRequest(WireModel): + """A request from the client to the server, to ask for completion options.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["completion/complete"] + params: CompleteRequestParams + + +class CompleteRequestParams(WireModel): + """Parameters for a `completion/complete` request.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + argument: Argument + """ + The argument's information + """ + context: Context | None = None + """ + Additional, optional context for completions + """ + ref: PromptReference | ResourceTemplateReference + + +class CreateMessageRequest(WireModel): + """A request from the server to sample an LLM via the client. The client has full discretion over which model to + select. The client should also inform the user before beginning sampling, to allow them to inspect the request + (human in the loop) and decide whether to approve it. + """ + + method: Literal["sampling/createMessage"] + params: CreateMessageRequestParams + + +class CreateMessageRequestParams(WireModel): + """Parameters for a `sampling/createMessage` request.""" + + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + The client MAY ignore this request. + + Default is `"none"`. The values `"thisServer"` and `"allServers"` are deprecated (SEP-2596): servers SHOULD + omit this field or use `"none"`, and SHOULD only use the deprecated values if the client declares + ClientCapabilities.sampling.context. + """ + max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. + """ + messages: list[SamplingMessage] + metadata: JSONObject | None = None + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ + temperature: float | None = None + tool_choice: Annotated[ToolChoice | None, Field(alias="toolChoice")] = None + """ + Controls how the model uses tools. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + Default is `{ mode: "auto" }`. + """ + tools: list[Tool] | None = None + """ + Tools that the model may use during generation. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + """ + + +class GetPromptRequest(WireModel): + """Used by the client to get a prompt provided by the server.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/get"] + params: GetPromptRequestParams + + +class GetPromptRequestParams(WireModel): + """Parameters for a `prompts/get` request.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + name: str + """ + The name of the prompt or prompt template. + """ + request_state: Annotated[str | None, Field(alias="requestState")] = None + + +class ListPromptsRequest(WireModel): + """Sent from the client to request a list of prompts and prompt templates the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/list"] + params: PaginatedRequestParams + + +class ListResourceTemplatesRequest(WireModel): + """Sent from the client to request a list of resource templates the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/templates/list"] + params: PaginatedRequestParams + + +class ListResourcesRequest(WireModel): + """Sent from the client to request a list of resources the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/list"] + params: PaginatedRequestParams + + +class ListRootsRequest(WireModel): + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ + + method: Literal["roots/list"] + params: RequestParams | None = None + + +class ListToolsRequest(WireModel): + """Sent from the client to request a list of tools the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/list"] + params: PaginatedRequestParams + + +class Data(WireModel): + """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + + required_capabilities: Annotated[ClientCapabilities, Field(alias="requiredCapabilities")] + """ + The capabilities the server requires from the client to process this request. + """ + + +class Error1(WireModel): + code: Literal[-32003] + """ + The error type that occurred. + """ + data: Data + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class PaginatedRequest(WireModel): + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: PaginatedRequestParams + + +class PaginatedRequestParams(WireModel): + """Common params for paginated requests.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class ReadResourceRequest(WireModel): + """Sent from the client to the server, to read a specific resource URI.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/read"] + params: ReadResourceRequestParams + + +class ReadResourceRequestParams(WireModel): + """Parameters for a `resources/read` request.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + request_state: Annotated[str | None, Field(alias="requestState")] = None + uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class RequestParams(WireModel): + """Common params for any request.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + + +class ResourceRequestParams(WireModel): + """Common params for resource-related requests.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class ServerCapabilities(WireModel): + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any server can define its own, additional capabilities. + """ + + completions: JSONObject | None = None + """ + Present if the server supports argument autocompletion suggestions. + """ + experimental: dict[str, JSONObject] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ + extensions: dict[str, JSONObject] | None = None + """ + Optional MCP extensions that the server supports. Keys are extension identifiers + (e.g., "io.modelcontextprotocol/tasks"), and values are per-extension settings + objects. An empty object indicates support with no settings. + """ + logging: JSONObject | None = None + """ + Present if the server supports sending log messages to the client. + """ + prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ + resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ + tools: Tools | None = None + """ + Present if the server offers any tools to call. + """ + + +# --- Aliases new or changed in 2026-07-28 --- +# (defined last: an alias right-hand side evaluates its referents at import) + +ResultType: TypeAlias = str + +ClientResult: TypeAlias = Result + +EmptyResult: TypeAlias = Result + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + +ElicitRequestParams: TypeAlias = ElicitRequestFormParams | ElicitRequestURLParams + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + +JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | SubscriptionsAcknowledgedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification + | ElicitationCompleteNotification +) + +ClientNotification: TypeAlias = CancelledNotification | ProgressNotification + +SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent + +InputResponse: TypeAlias = CreateMessageResult | ListRootsResult | ElicitResult + +InputResponses: TypeAlias = dict[str, InputResponse] + +ServerResult: TypeAlias = ( + Result + | InputRequiredResult + | DiscoverResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) + +InputRequest: TypeAlias = CreateMessageRequest | ListRootsRequest | ElicitRequest + +ClientRequest: TypeAlias = ( + DiscoverRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscriptionsListenRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | CompleteRequest +) + +InputRequests: TypeAlias = dict[str, InputRequest] + +JSONArray: TypeAlias = list["JSONValue"] + +CompleteResultResponse.model_rebuild() +ListPromptsResultResponse.model_rebuild() +ListResourceTemplatesResultResponse.model_rebuild() +ListResourcesResultResponse.model_rebuild() +ListToolsResultResponse.model_rebuild() +CallToolResultResponse.model_rebuild() +DiscoverRequest.model_rebuild() +DiscoverResult.model_rebuild() +GetPromptResultResponse.model_rebuild() +InputRequiredResult.model_rebuild() +InputResponseRequestParams.model_rebuild() +MissingRequiredClientCapabilityError.model_rebuild() +ReadResourceResultResponse.model_rebuild() +RequestMetaObject.model_rebuild() +SubscriptionsListenRequest.model_rebuild() +RequestedSchema.model_rebuild() +PromptMessage.model_rebuild() +ToolResultContent.model_rebuild() +CallToolResult.model_rebuild() +ElicitRequest.model_rebuild() +CreateMessageResult.model_rebuild() +SamplingMessage.model_rebuild() +CallToolRequest.model_rebuild() +CallToolRequestParams.model_rebuild() +Elicitation.model_rebuild() +Sampling.model_rebuild() +ClientCapabilities.model_rebuild() +CompleteRequest.model_rebuild() +CompleteRequestParams.model_rebuild() +CreateMessageRequest.model_rebuild() +CreateMessageRequestParams.model_rebuild() +GetPromptRequest.model_rebuild() +GetPromptRequestParams.model_rebuild() +ListPromptsRequest.model_rebuild() +ListResourceTemplatesRequest.model_rebuild() +ListResourcesRequest.model_rebuild() +ListRootsRequest.model_rebuild() +ListToolsRequest.model_rebuild() +PaginatedRequest.model_rebuild() +PaginatedRequestParams.model_rebuild() +ReadResourceRequest.model_rebuild() +ReadResourceRequestParams.model_rebuild() +ServerCapabilities.model_rebuild() diff --git a/src/mcp/types/wire.py b/src/mcp/types/wire.py new file mode 100644 index 0000000000..e1ac8d8e18 --- /dev/null +++ b/src/mcp/types/wire.py @@ -0,0 +1,733 @@ +"""Version-aware wire boundary for MCP types. + +Serialize a monolith model for, or parse wire data under, a specific +negotiated protocol version. A "monolith" model is one of the version-superset +models in ``mcp.types._types``: one class per protocol construct, carrying +every supported version's fields, in contrast to the per-version wire-shape +packages (``mcp.types.v*``). The unique key for every behavior in this module +is (monolith type, negotiated version). Versions are opaque strings ordered by +``KNOWN_PROTOCOL_VERSIONS``; nothing here negotiates, dispatches, or holds +session state. + +Emission works by re-validation: ``serialize_for`` dumps the monolith model, +applies a handful of explicit shaping rules (each cited inline), validates the +result through the negotiated version's wire-shape models (``mcp.types.v*`` — +imported lazily, so this module stays cheap until first use), and re-dumps. +The re-dump decides only which keys survive; emitted values always come from +the original dump. A version's required fields, union memberships, and field +existence are therefore facts readable in that version's model package rather +than rules written here. Parsing is one lenient superset parse at every +version plus the few documented 2026-07-28 inbound mandates. +""" + +from __future__ import annotations + +import importlib +from collections.abc import Mapping +from functools import cache +from types import ModuleType +from typing import Any, Final, TypeVar, cast, get_args, overload + +from pydantic import BaseModel, TypeAdapter, ValidationError +from pydantic_core import InitErrorDetails, PydanticCustomError + +from mcp.shared.version import KNOWN_PROTOCOL_VERSIONS, is_version_at_least +from mcp.types._spec_names import SDK_TO_SCHEMA_RENAMES +from mcp.types._types import ( + CLIENT_CAPABILITIES_META_KEY, + CLIENT_INFO_META_KEY, + PROTOCOL_VERSION_META_KEY, + AudioContent, + CacheableResult, + ElicitResult, + EmbeddedResource, + EmptyResult, + ImageContent, + InputRequiredResult, + Notification, + Request, + RequestParams, + ResourceLink, + Result, + ResultType, + TextContent, + ToolResultContent, + ToolUseContent, +) +from mcp.types._versions import ( + CLIENT_NOTIFICATION_METHODS, + CLIENT_REQUEST_METHODS, + SERVER_NOTIFICATION_METHODS, + SERVER_REQUEST_METHODS, +) +from mcp.types.jsonrpc import JSONRPCError, JSONRPCNotification, JSONRPCRequest, JSONRPCResponse + +__all__ = [ + "CLIENT_NOTIFICATION_METHODS", + "CLIENT_REQUEST_METHODS", + "KNOWN_PROTOCOL_VERSIONS", + "SERVER_NOTIFICATION_METHODS", + "SERVER_REQUEST_METHODS", + "UnknownProtocolVersionError", + "UnsupportedAtVersionError", + "parse_as", + "serialize_for", +] + +# KNOWN_PROTOCOL_VERSIONS (the ordered version registry) lives in +# mcp.shared.version; the four per-version method tables are plain data in +# mcp.types._versions. Both are re-exported here as the boundary's public +# surface. The tables record which methods exist at each version; acting on +# that (rejecting a version-invalid request, dropping or logging a +# version-invalid notification) is session-layer behavior, as are the other +# capability-keyed halves of version shaping: the sampling tools capability +# gate for tool content on 2025-11-25 sessions, the elicitation url capability +# gate, and the per-request logLevel send condition. + +_T = TypeVar("_T", bound=BaseModel) + + +class UnknownProtocolVersionError(ValueError): + """``version`` is not a known protocol version (raised on emission only). + + Inbound parsing never raises this: an unknown version is most plausibly + newer than this SDK, and lenient parsing cannot misrepresent data. On + emission the type layer must never guess a wire shape, so + ``serialize_for`` refuses instead. + """ + + def __init__(self, version: str) -> None: + super().__init__(f"unknown protocol version {version!r}; known versions: {', '.join(KNOWN_PROTOCOL_VERSIONS)}") + self.version: str = version + self.known: tuple[str, ...] = KNOWN_PROTOCOL_VERSIONS + + +class UnsupportedAtVersionError(ValueError): + """The value cannot be legally represented on this version's wire. + + Raised by ``serialize_for`` instead of silently changing meaning: for + example an ``InputRequiredResult`` on a 2025-11-25-or-earlier session, + tool-block or multi-block sampling content on 2025-06-18 or earlier, a + ``CancelledNotification`` without ``request_id`` on 2025-06-18 or earlier, + or a 2026-07-28 client request whose ``params._meta`` lacks the + caller-supplied ``clientInfo``/``clientCapabilities`` entries. + """ + + def __init__(self, message: str, *, version: str) -> None: + super().__init__(message) + self.version: str = version + + +_VERSION_MODULES: Final[Mapping[str, str]] = { + "2024-11-05": "mcp.types.v2024_11_05", + "2025-03-26": "mcp.types.v2025_03_26", + "2025-06-18": "mcp.types.v2025_06_18", + "2025-11-25": "mcp.types.v2025_11_25", + "2026-07-28": "mcp.types.v2026_07_28", +} +"""Module path of each known version's wire-shape model package.""" + +# serialize_for-only name aliases for SDK classes whose wire shape the schemas +# publish under a different export name and that the spec-name divergence map +# cannot carry (the map requires schema counterparts; these are SDK-only +# names). The SDK splits the wide-content sampling result into its own class, +# while the 2025-11-25 and 2026-07-28 schemas type the class they name +# CreateMessageResult wide. +_WIRE_NAME_ALIASES: Final[Mapping[str, str]] = {"CreateMessageResultWithTools": "CreateMessageResult"} + +_ENVELOPE_MODELS: Final = (JSONRPCRequest, JSONRPCNotification, JSONRPCResponse, JSONRPCError) +_BODY_MODELS: Final = (Request, Notification, Result) + +# From 2025-11-25 on, the schemas define each request and notification as a +# complete JSON-RPC frame, so the generated wire-shape classes declare the +# envelope fields (requests: jsonrpc + id; notifications: jsonrpc) as +# required. serialize_for emits message BODIES; validation supplies these +# constants and the output never carries them (dropped before alignment). +_ENVELOPE_FIELD_STUBS: Final[Mapping[str, Any]] = {"jsonrpc": "2.0", "id": 0} + +_SANCTIONED_STRIPS: Final[frozenset[str]] = frozenset( + { + # resultType is new in 2026-07-28; on earlier versions even a + # caller-set value is dropped — deployed peers hard-reject an empty + # result that carries any extra key (deployed-peer-mandated). + "resultType", + # The caching directives are new in 2026-07-28. + "ttlMs", + "cacheScope", + # The capabilities extensions field is new in 2026-07-28 and must not + # leak by default on earlier versions. + "extensions", + # Task-augmented params and the capabilities tasks subtrees exist only + # in the 2025-11-25 schema. + "task", + "tasks", + } +) +"""Keys whose loss during re-validation is a sanctioned strip. + +Any other key the target version's model dropped is RESTORED by the alignment +walk: newer optional fields on known types (``icons``, ``title``, ...) are +wire-safe against every deployed peer and pass through at every version, and +values are never substituted — the re-validated output decides keys only. +""" + +_EMBEDDED_PAYLOAD_KEYS: Final[frozenset[str]] = frozenset({"inputRequests", "inputResponses"}) +"""Wire keys of maps whose values are embedded request/response payloads. + +The boundary deliberately never reshapes embedded payloads — no injection and +no strip; below these keys every key the re-dump lost is restored verbatim. +Embedded-payload hygiene is the caller's responsibility. +""" + +# The 2026-07-28 schema requires the reserved _meta entries on every client +# request, so the injection/validation rule is keyed on that revision's client +# request methods. +_REQUIRED_META_METHODS: Final[frozenset[str]] = CLIENT_REQUEST_METHODS["2026-07-28"] +_REQUIRED_META_KEYS: Final[tuple[str, ...]] = ( + PROTOCOL_VERSION_META_KEY, + CLIENT_INFO_META_KEY, + CLIENT_CAPABILITIES_META_KEY, +) + +_RECOGNIZED_RESULT_TYPES: Final[frozenset[str]] = frozenset( + literal for arm in get_args(ResultType) for literal in get_args(arm) +) +"""The ``resultType`` values the spec names, read from the monolith +``ResultType`` alias so the recognized set has a single source.""" + +_CONTENT_BLOCK_TAGS: Final[Mapping[str, Any]] = { + block.__name__: block.model_fields["type"].default + for block in ( + TextContent, + ImageContent, + AudioContent, + ResourceLink, + EmbeddedResource, + ToolUseContent, + ToolResultContent, + ) +} +"""Every monolith content-block class and its wire ``type`` tag.""" + + +def serialize_for(model: BaseModel, version: str) -> dict[str, Any]: + """Dump ``model`` as its wire JSON for a session negotiated at ``version``. + + ``model`` is a top-level message body (a concrete request, notification, + or result model) or a ``mcp.types.jsonrpc`` envelope model; any other + monolith model (a bare fragment: content blocks, ``SamplingMessage``, + capabilities objects, params classes, ...) raises ``TypeError`` — + fragments are shaped only in situ, inside the body that carries them. + + Returns the message body (requests/notifications/results) or the full + frame when given an envelope model. Shaping is version-keyed: injections + fire only on the versions that require a construct, and strips fire on + the versions that lack it — whether the version predates the construct + (``resultType`` before 2026-07-28) or postdates its removal (``task`` + metadata and the roots capability's ``listChanged`` flag at 2026-07-28). + The mechanism decides KEYS only: emitted leaf values always come from the + monolith dump, never from a version package's re-validated output. For + values that use only constructs the target version defines, dumps for + 2025-11-25 and earlier are byte-identical to a plain + ``model_dump(by_alias=True, mode="json", exclude_none=True)`` of the same + value; a caller-set field the target version lacks (``resultType``, + ``ttlMs``, ``cacheScope``, capability ``extensions``, ...) is stripped + there, so the dump then differs from the plain one by exactly that strip. + Null-valued elicitation content entries — constructible for v1.x + compatibility, typed by no schema version — are caller data and pass + through verbatim at every version that models elicitation. + + On 2026-07-28 sessions, client requests must already carry the + caller-supplied ``clientInfo`` and ``clientCapabilities`` entries in + ``params._meta``; sourcing session identity is the session layer's job, + and the boundary injects only ``protocolVersion``. + + A re-validation failure is reported as ``UnsupportedAtVersionError`` and + cannot distinguish a value the target revision truly cannot express from a + defect in that revision's model package; when a raise surprises you, read + the chained validation error and check the ``mcp.types.v*`` package first. + + Raises: + UnknownProtocolVersionError: ``version`` is not a known protocol + version. + UnsupportedAtVersionError: ``model`` has no legal wire form at + ``version`` — its type or content does not exist in that + revision's schema, or a 2026-07-28 client request is missing the + caller-supplied identity entries above. + """ + if not _is_serializable_payload(model): + raise TypeError("serialize_for expects a message body or an envelope model") + if version not in KNOWN_PROTOCOL_VERSIONS: + raise UnknownProtocolVersionError(version) + dump = model.model_dump(by_alias=True, mode="json", exclude_none=True) + if isinstance(model, _ENVELOPE_MODELS): + # Envelope frames are version-independent, and the untyped + # params/result interior of a generic envelope passes through opaque: + # payload shaping happens when the typed payload model itself is + # serialized, never by inspecting an untyped dict. + return dump + shaped = _shape_for_version(model, dump, version) + wire_cls = _wire_class(type(model), version) + stubs = {key: value for key, value in _ENVELOPE_FIELD_STUBS.items() if key in wire_cls.model_fields} + try: + revalidated = wire_cls.model_validate({**stubs, **_revalidation_view(model, shaped)}) + except ValidationError as err: + raise UnsupportedAtVersionError( + f"{type(model).__name__} has no legal wire form at protocol version {version}: {_summarize(err)}", + version=version, + ) from err + redump = revalidated.model_dump(by_alias=True, mode="json", exclude_unset=True) + for key in stubs: + del redump[key] + return _merge_and_align(shaped, redump) + + +def _is_serializable_payload(model: BaseModel) -> bool: + """True when ``model`` is in ``serialize_for``'s payload domain.""" + return isinstance(model, _BODY_MODELS) or isinstance(model, _ENVELOPE_MODELS) + + +def _shape_for_version(model: BaseModel, dump: dict[str, Any], version: str) -> dict[str, Any]: + """Apply the hand-written emission rules to a monolith body dump. + + Five rules, all keyed to 2026-07-28; everything else about a version's + wire shape (required fields, union membership, dropped fields) is read + from that version's model package by re-validation. The rules touch the + top-level body only — embedded request/response payloads (the + ``inputRequests``/``inputResponses`` map values) are never recursed into. + """ + # OD-11 alternative: narrow outbound values to the target revision's declared shapes on older versions. + if not is_version_at_least(version, "2026-07-28"): + return dump + if isinstance(model, Result): + # resultType is required on 2026-07-28 results; absent means + # "complete", and an input-required result must say so. A caller-set + # value is never overwritten. + dump.setdefault("resultType", "input_required" if isinstance(model, InputRequiredResult) else "complete") + if isinstance(model, CacheableResult): + # ttlMs/cacheScope are required on these results from 2026-07-28; + # when the handler leaves them unset the boundary fills the + # don't-cache pair: immediately stale, single-user scope. + # OD-5 alternative: inject nothing and require handlers to set both fields. + dump.setdefault("ttlMs", 0) + dump.setdefault("cacheScope", "private") + if isinstance(model, Request) and dump.get("method") in _REQUIRED_META_METHODS: + # 2026-07-28 client requests carry the reserved _meta entries. + # protocolVersion is the one entry derivable here and is merged + # without overwriting a caller-set value; clientInfo and + # clientCapabilities are session identity, never synthesized — when + # absent, re-validation below refuses loudly. + params: dict[str, Any] = dump.setdefault("params", {}) + meta: dict[str, Any] = params.setdefault("_meta", {}) + meta.setdefault(PROTOCOL_VERSION_META_KEY, version) + if isinstance(model, Request): + # 2026-07-28 removed the roots capability's listChanged flag; the + # capability itself survives and emits without it. The schema types + # the capability as a plain object, so re-validation alone cannot + # drop the flag. + meta_value = _dict_value(dump, "params", "_meta") + capabilities = _dict_value(meta_value, CLIENT_CAPABILITIES_META_KEY) if meta_value is not None else None + roots = _dict_value(capabilities, "roots") if capabilities is not None else None + if roots is not None: + roots.pop("listChanged", None) + if isinstance(model, InputRequiredResult) and model.input_requests is None and model.request_state is None: + # The 2026-07-28 schema requires at least one of + # inputRequests/requestState on the wire; the requirement is spec + # prose (both fields are optional in the schema's type), so + # re-validation cannot enforce it. + raise UnsupportedAtVersionError( + "InputRequiredResult with neither input_requests nor request_state set " + f"has no legal wire form at protocol version {version}", + version=version, + ) + return dump + + +def _revalidation_view(model: BaseModel, shaped: dict[str, Any]) -> dict[str, Any]: + """The shaped dump as re-validation sees it; the output base is untouched. + + Null-valued elicitation content entries are withheld from the dict handed + to the version package: no schema version types a null form answer (the + monolith admits ``None`` values for v1.x constructor compatibility), and + emitted values are caller data the boundary passes through verbatim + rather than narrowing or refusing — python v1.x itself constructs, + accepts, and emits the same body. The withheld entries are keys the + re-dump lost, so the alignment walk restores them from the shaped dump. + Every other value reaches the version package unchanged: a value the + target version truly cannot express still refuses loudly. + """ + if not isinstance(model, ElicitResult): + return shaped + content = shaped.get("content") + if not isinstance(content, dict): + return shaped + entries = cast("dict[str, Any]", content) + if all(value is not None for value in entries.values()): + return shaped + view = dict(shaped) + view["content"] = {key: value for key, value in entries.items() if value is not None} + return view + + +def _dict_value(mapping: Mapping[str, Any], *keys: str) -> dict[str, Any] | None: + """Walk ``keys`` through nested dicts; ``None`` as soon as one is not a dict.""" + value: Any = mapping + for key in keys: + if not isinstance(value, dict): + return None + value = cast("dict[str, Any]", value).get(key) + return cast("dict[str, Any]", value) if isinstance(value, dict) else None + + +def _version_module(version: str) -> ModuleType: + """Import (on first use) and return the wire-shape models for ``version``. + + Loaded lazily so importing ``mcp.types`` (or this module) never pays for + the per-version model packages; ``sys.modules`` caches after the first + boundary call for a version. + """ + return importlib.import_module(_VERSION_MODULES[version]) + + +def _wire_class(cls: type[BaseModel], version: str) -> type[BaseModel]: + """Return the ``version`` package's model class for the monolith ``cls``. + + Lookup is by name: the monolith name itself, then the schema-side name + where the SDK deliberately diverges. A miss means the type has no wire + form at that version (its schema does not define it). + """ + module = _version_module(version) + for name in (cls.__name__, SDK_TO_SCHEMA_RENAMES.get(cls.__name__), _WIRE_NAME_ALIASES.get(cls.__name__)): + if name is not None: + found = getattr(module, name, None) + if found is not None: + return found + raise UnsupportedAtVersionError(f"{cls.__name__} has no wire form at protocol version {version}", version=version) + + +def _summarize(err: ValidationError) -> str: + """One line for the first validation error (the full chain is preserved).""" + first = err.errors()[0] + where = ".".join(str(segment) for segment in first["loc"]) or "" + remainder = f" (+{err.error_count() - 1} more)" if err.error_count() > 1 else "" + return f"{where}: {first['msg']}{remainder}" + + +def _merge_and_align(shaped: dict[str, Any], redump: dict[str, Any], strips_apply: bool = True) -> dict[str, Any]: + """Merge the re-validated dump back onto the shaped monolith dump. + + The re-dump decides KEYS only — values are never substituted: every + emitted leaf value comes from the shaped monolith dump, never from the + re-validated model, whose validation may have coerced a value (an int + bound re-rendered through a float field, or vice versa). The output + follows the shaped dump's key order: pydantic re-dumps in model field + order, while dumps for 2025-11-25 and earlier must stay byte-identical to + the monolith dump. A key the re-dump lost is restored unless its loss is + a sanctioned strip (and never inside embedded payload maps, where + everything is restored); a key the re-dump invented is always a defect in + the version package. + """ + merged: dict[str, Any] = {} + for key, shaped_value in shaped.items(): + if key not in redump: + if not (strips_apply and key in _SANCTIONED_STRIPS): + merged[key] = shaped_value + continue + merged[key] = _align_value(shaped_value, redump[key], strips_apply and key not in _EMBEDDED_PAYLOAD_KEYS) + invented = redump.keys() - shaped.keys() + if invented: + raise RuntimeError(f"re-validation for the target version invented output keys: {sorted(invented)}") + return merged + + +def _align_value(shaped_value: Any, redump_value: Any, strips_apply: bool) -> Any: + """Walk shaped and re-dumped values in parallel; the shaped side wins at leaves.""" + if isinstance(redump_value, dict) and isinstance(shaped_value, dict): + return _merge_and_align( + cast("dict[str, Any]", shaped_value), cast("dict[str, Any]", redump_value), strips_apply + ) + if isinstance(redump_value, list) and isinstance(shaped_value, list): + shaped_items = cast("list[Any]", shaped_value) + redump_items = cast("list[Any]", redump_value) + return [ + _align_value(shaped_item, redump_item, strips_apply) + for shaped_item, redump_item in zip(shaped_items, redump_items, strict=True) + ] + return shaped_value + + +@overload +def parse_as(type_: type[_T], data: Mapping[str, Any], version: str) -> _T: ... +@overload +def parse_as(type_: Any, data: Mapping[str, Any], version: str) -> Any: ... +def parse_as(type_: Any, data: Mapping[str, Any], version: str) -> Any: + """Validate inbound wire ``data`` as ``type_`` under ``version`` semantics. + + ``type_`` is a monolith model class or a public union alias + (``ClientRequest``, ``ServerResult``, ``ContentBlock``, + ``JSONRPCMessage``, ...). Parsing is one lenient superset parse at every + version — unknown fields are never rejected — plus the 2026-07-28 + inbound mandates: a result carrying an unrecognized ``resultType`` value + is rejected, a client request must carry all three reserved ``_meta`` + entries, and embedded input-request entries must each carry ``method``. + Result-bearing unions resolve their member structurally: every arm + recognizing more of the payload's top-level keys than the base ``Result`` + is a candidate, candidates are validated best match first, and the first + success wins — so the open-shaped ``EmptyResult`` arm cannot mask a + better-matching member's validation failures, and a body its best-looking + arm rejects still parses when a sibling arm accepts it. When every + candidate fails, the best-ranked arm's errors surface; unknown-shaped + result bodies still parse (as the ``EmptyResult`` arm). Unknown + ``version`` strings parse leniently with NO version-keyed mandates + applied, and never raise for the version string itself. + + Raises: + pydantic.ValidationError: ``data`` is not valid for ``type_`` at + ``version``. + """ + apply_mandates = is_version_at_least(version, "2026-07-28") + result_arms = _result_union_arms(type_) + if result_arms is None: + parsed = _validate_refined(type_, data) + elif apply_mandates and InputRequiredResult in result_arms and data.get("resultType") == "input_required": + # 2026-07-28 response bodies discriminate by resultType: an + # input-required body must resolve to InputRequiredResult even when it + # also carries fields of another member. Union targets only — a + # concrete `type_` always parses as the requested class. + parsed = _validate_refined(InputRequiredResult, data) + else: + parsed = _validate_first(_select_result_arms(result_arms, data), data) + if apply_mandates: + _reject_unrecognized_result_type(type_, data) + _reject_input_request_entries_without_method(parsed) + _reject_missing_required_meta(parsed) + return parsed + + +@cache +def _adapter(type_: Any) -> TypeAdapter[Any]: + """One ``TypeAdapter`` per parse target, cached on the type object.""" + return TypeAdapter[Any](type_) + + +@cache +def _result_union_arms(type_: Any) -> tuple[type[Result], ...] | None: + """The member tuple when ``type_`` is a union of ``Result`` subclasses + (``ServerResult``, ``ClientResult``); ``None`` for anything else.""" + arms = get_args(type_) + if arms and all(isinstance(arm, type) and issubclass(arm, Result) for arm in arms): + return cast("tuple[type[Result], ...]", arms) + return None + + +@cache +def _wire_field_names(cls: type[BaseModel]) -> frozenset[str]: + """A model's wire-facing key set: each field's alias when it has one.""" + return frozenset(field.alias or name for name, field in cls.model_fields.items()) + + +def _select_result_arms(arms: tuple[type[Result], ...], data: Mapping[str, Any]) -> tuple[type[Result], ...]: + """Rank the result-union members that could own the payload, best first. + + A plain smart-union parse cannot do this job: ``EmptyResult`` declares no + required fields, so it validates EVERY JSON object and would swallow the + validation failures of a better-matching member — a discover result + missing its required ``supportedVersions`` and a tool result whose content + carries an unknown ``type`` must reject, not quietly fall back to + ``EmptyResult``. Every arm recognizing strictly more of the payload's + top-level keys than the base ``Result`` fields is a candidate, ranked by + recognized-key count with ties kept in union declaration order. Key + counting cannot tell apart sibling arms with identical top-level key sets + (the single-content and array-content sampling results differ only in the + SHAPE of ``content``), so the caller validates candidates in rank order + and the first success wins. When no arm beats base ``Result`` the body + parses as the ``EmptyResult`` arm. The selection is version-free and + ignores unknown fields, so inbound leniency is untouched. + """ + payload_keys = frozenset(data) + base = len(payload_keys & _wire_field_names(Result)) + scores = {arm: len(payload_keys & _wire_field_names(arm)) for arm in arms} + candidates = sorted((arm for arm in arms if scores[arm] > base), key=lambda arm: -scores[arm]) + return tuple(candidates) if candidates else (EmptyResult,) + + +def _validate_first(targets: tuple[type[Result], ...], data: Mapping[str, Any]) -> Any: + """Validate ``data`` against each target in order; the first success wins. + + When every target rejects, the surfaced error is the FIRST target's: the + best-ranked candidate is the arm the payload most resembles, so its + errors (refined exactly like a single-target parse) describe the failure. + """ + first, *rest = targets + try: + return _validate_refined(first, data) + except ValidationError: + for target in rest: + try: + return _validate_refined(target, data) + except ValidationError: + continue + raise + + +def _validate_refined(type_: Any, data: Mapping[str, Any]) -> Any: + """Superset-parse ``data`` as ``type_``, refining unknown content tags.""" + try: + return _adapter(type_).validate_python(data) + except ValidationError as err: + refined = _refine_unknown_content_type(err, data) + if refined is None: + raise + raise refined from err + + +def _refine_unknown_content_type(err: ValidationError, data: Mapping[str, Any]) -> ValidationError | None: + """Convert per-arm failures on an unknown content ``type`` tag into a + single unknown-tag error at the failing location. + + The monolith content unions are plain unions (their pre-2026 shape), so an + unknown ``type`` value fails every arm with per-arm structural errors + rather than one tag error — but an unknown content type is an unknown + union member to every deployed SDK, including when it fails nested inside + a parsed result's ``content`` list. Only a dict whose ``type`` value is a + string outside the failing arms' tag set is converted: a recognized tag + with bad fields, and a tag-less entry (e.g. an input-request entry with no + ``method``), keep their structural errors. + + A plain-union error location is ``(..., "", "")`` + (verified against pydantic 2.12); the arm-name segment is how + content-union failures are recognized here. + """ + failing_locations: dict[tuple[str | int, ...], set[str]] = {} + for line in err.errors(): + location = line["loc"] + for index, segment in enumerate(location): + if isinstance(segment, str) and segment in _CONTENT_BLOCK_TAGS: + failing_locations.setdefault(location[:index], set()).add(segment) + break + line_errors: list[InitErrorDetails] = [] + for location, arm_names in failing_locations.items(): + fragment: Any = data + for segment in location: + fragment = fragment[segment] + if not isinstance(fragment, dict): + continue + tag = cast("dict[str, Any]", fragment).get("type") + expected_tags = sorted(str(_CONTENT_BLOCK_TAGS[name]) for name in arm_names) + if isinstance(tag, str) and tag not in expected_tags: + line_errors.append( + InitErrorDetails( + type=PydanticCustomError( + "union_tag_invalid", + "Input tag '{tag}' found using {discriminator} does not match any of the " + "expected tags: {expected_tags}", + { + "discriminator": "'type'", + "tag": tag, + "expected_tags": ", ".join(repr(expected) for expected in expected_tags), + }, + ), + loc=location, + input=fragment, + ) + ) + if not line_errors: + return None + return ValidationError.from_exception_data(err.title, line_errors) + + +def _reject_unrecognized_result_type(type_: Any, data: Mapping[str, Any]) -> None: + """2026-07-28 inbound mandate: an unrecognized ``resultType`` rejects. + + Applies when the parse target is a ``Result`` class or a result-bearing + union — a stray ``resultType`` key on a request or any other type is an + ordinary unknown field and stays accepted. An absent field is accepted + (the spec defines absence as "complete") and a recognized value is + retained; only a present-and-unrecognized string value rejects, with the + pinned error type ``result_type_invalid``. + """ + if not _is_result_parse_target(type_): + return + value = data.get("resultType") + if isinstance(value, str) and value not in _RECOGNIZED_RESULT_TYPES: + raise ValidationError.from_exception_data( + getattr(type_, "__name__", "Result"), + [ + InitErrorDetails( + type=PydanticCustomError( + "result_type_invalid", + "unrecognized resultType {result_type}; this protocol version defines " + "'complete' and 'input_required'", + {"result_type": value}, + ), + loc=("resultType",), + input=value, + ) + ], + ) + + +def _reject_input_request_entries_without_method(parsed: Any) -> None: + """2026-07-28 inbound mandate: embedded input-request entries carry + ``method``. + + The values of an input-required result's ``inputRequests`` map are full + request payloads, and the schema requires ``method`` on every request. The + monolith request models default their method literal (so handler code can + construct them without boilerplate), which would let a method-less entry + quietly validate as the one member whose remaining fields are all + optional; the mandate instead checks that every entry actually supplied + the field. A missing ``method`` is a structural failure (plain ``missing`` + error), not an unknown union member. + """ + if not isinstance(parsed, InputRequiredResult) or parsed.input_requests is None: + return + missing = [key for key, entry in parsed.input_requests.items() if "method" not in entry.model_fields_set] + if missing: + raise ValidationError.from_exception_data( + type(parsed).__name__, + [InitErrorDetails(type="missing", loc=("inputRequests", key, "method"), input=None) for key in missing], + ) + + +def _is_result_parse_target(type_: Any) -> bool: + """True when ``type_`` is a ``Result`` class or a result-bearing union.""" + if isinstance(type_, type): + return issubclass(cast("type[object]", type_), Result) + return _result_union_arms(type_) is not None + + +def _reject_missing_required_meta(parsed: Any) -> None: + """2026-07-28 inbound mandate: client requests carry the reserved + ``_meta`` triple. + + Every 2026-07-28 client request requires the + ``io.modelcontextprotocol/{protocolVersion,clientInfo,clientCapabilities}`` + entries in ``params._meta``, each independently; a missing ``params``, a + missing ``_meta``, or any missing entry rejects with the pinned error type + ``missing_required_meta`` (one error per missing entry). + """ + if not isinstance(parsed, Request): + return + request = cast("Request[Any, Any]", parsed) + if request.method not in _REQUIRED_META_METHODS: + return + params = request.params + meta: Mapping[str, Any] = params.meta if isinstance(params, RequestParams) and params.meta is not None else {} + missing = [key for key in _REQUIRED_META_KEYS if key not in meta] + if missing: + raise ValidationError.from_exception_data( + type(request).__name__, + [ + InitErrorDetails( + type=PydanticCustomError( + "missing_required_meta", + "required reserved _meta entry {meta_key} is missing", + {"meta_key": key}, + ), + loc=("params", "_meta", key), + input=dict(meta), + ) + for key in missing + ], + ) diff --git a/tests/spec_oracles/PINNED.json b/tests/spec_oracles/PINNED.json new file mode 100644 index 0000000000..fbc2bf386c --- /dev/null +++ b/tests/spec_oracles/PINNED.json @@ -0,0 +1,54 @@ +{ + "generator": { + "package": "datamodel-code-generator", + "version": "0.57.0" + }, + "spec_repo": "modelcontextprotocol/modelcontextprotocol", + "versions": { + "2024-11-05": { + "sha": "6d441518de8a9d5adbab0b10a76a667a63f90665", + "schema_path": "schema/2024-11-05/schema.json", + "protocol_version": "2024-11-05", + "module": "v2024_11_05", + "frozen": true + }, + "2025-03-26": { + "sha": "6d441518de8a9d5adbab0b10a76a667a63f90665", + "schema_path": "schema/2025-03-26/schema.json", + "protocol_version": "2025-03-26", + "module": "v2025_03_26", + "frozen": true + }, + "2025-06-18": { + "sha": "6d441518de8a9d5adbab0b10a76a667a63f90665", + "schema_path": "schema/2025-06-18/schema.json", + "protocol_version": "2025-06-18", + "module": "v2025_06_18", + "frozen": true + }, + "2025-11-25": { + "sha": "6d441518de8a9d5adbab0b10a76a667a63f90665", + "schema_path": "schema/2025-11-25/schema.json", + "protocol_version": "2025-11-25", + "module": "v2025_11_25", + "frozen": true + }, + "draft": { + "sha": "6d441518de8a9d5adbab0b10a76a667a63f90665", + "schema_path": "schema/draft/schema.json", + "protocol_version": "2026-07-28", + "module": "v2026_07_28", + "frozen": false + } + }, + "extensions": { + "tasks": { + "repo": "modelcontextprotocol/experimental-ext-tasks", + "sha": "dd47977f4e4069aa4147d816f52ebb9a27c11315", + "schema_path": "schema/draft/schema.json", + "protocol_version": null, + "module": "ext_tasks", + "frozen": false + } + } +} diff --git a/tests/spec_oracles/__init__.py b/tests/spec_oracles/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/spec_oracles/_base.py b/tests/spec_oracles/_base.py new file mode 100644 index 0000000000..3c636d7d61 --- /dev/null +++ b/tests/spec_oracles/_base.py @@ -0,0 +1,14 @@ +"""Base class for the generated spec-oracle models in this directory.""" + +from pydantic import BaseModel, ConfigDict + + +class OracleModel(BaseModel): + """Base for generated spec-oracle models. + + No alias generator on purpose: every wire alias must be explicit in the + generated code so the oracle cannot inherit (or mask) SDK serialization + behavior. + """ + + model_config = ConfigDict(populate_by_name=True, extra="allow") diff --git a/tests/spec_oracles/_harness.py b/tests/spec_oracles/_harness.py new file mode 100644 index 0000000000..4d34a4d46c --- /dev/null +++ b/tests/spec_oracles/_harness.py @@ -0,0 +1,747 @@ +"""Comparison library for the spec-oracle burn-down harness. + +Compares the generated, SHA-pinned oracle modules in this directory against the +SDK's hand-curated types (`mcp.types`, with `mcp.types.jsonrpc` as a secondary +search module). Every divergence becomes a `Finding`; hard findings fail the +gate in `test_burndown.py` unless they are allowlisted in +`burndown_allowlist.json`, and allowlisted entries that no longer fire are +*stale* and also fail the gate (the two-way ratchet that makes the allowlist a +burn-down list rather than a suppression file). + +Finding ids are stable strings: ``/[.]#``. +Aggregated findings (checks that run once across all oracles) use the +pseudo-oracle ``sdk``. + +Checks: + +Hard (fail unless allowlisted): +- SPEC-TYPE-MISSING: oracle def has no SDK counterpart. +- SPEC-FIELD-MISSING: oracle model field's wire name absent on the SDK model. +- SDK-REQUIRED-NOT-IN-SPEC: SDK requires a wire field this version's oracle + does not require (optional or absent) - the inbound-leniency invariant. +- TYPE-NARROWER: SDK annotation provably rejects values the oracle accepts. +- SDK-TYPE-PHANTOM (aggregated): SDK public type maps to no def in any oracle + and is not machinery. +- SDK-FIELD-PHANTOM (aggregated): SDK model field's wire name appears in no + version's oracle for the paired def. + +Soft (reported, never fail): +- SPEC-FIELD-OPTIONAL-IN-SDK: oracle requires, SDK optional (expected superset + behavior). +- TYPE-WIDER: SDK widens (adds `| None`, `Any`, superset Literal, ...). +- TYPE-INCOMPARABLE: the type algebra cannot relate the two annotations. + +Allowlist pseudo-checks (never emitted as findings; validated by +`schema_gap_applies` instead so the ratchet still covers them): +- VACUOUS-SCHEMA: the ext-tasks schema lost a core `$ref` (vacuous `anyOf`), + so the oracle faithfully says `Any` there. +- REQUIRED-UNVERIFIABLE: the ext-tasks schema lost the `required` array on a + Result-intersection def, so the oracle has every field optional. + +Allowlist categories (each entry's `reason` must stand on its own — it is +the only record a reader gets): +- not-yet-implemented: planned SDK work; the entry burns down when it lands. +- deliberate-deviation: a reviewed divergence the SDK keeps on purpose. +- schema-gap: the pinned schema itself is defective at this site (see the + pseudo-checks above). +- suspected-sdk-bug: the SDK side looks wrong; the reason must describe the + suspected defect and what would resolve it. +""" + +from __future__ import annotations + +import importlib +import json +import types +from dataclasses import dataclass +from pathlib import Path +from types import ModuleType +from typing import Any, Literal, Union, get_args, get_origin + +from pydantic import AnyUrl, Base64Str, BaseModel, FileUrl +from pydantic.fields import FieldInfo +from typing_extensions import TypeAliasType, is_typeddict + +import mcp.types +import mcp.types.jsonrpc +from mcp.types._spec_names import SDK_TO_SCHEMA_RENAMES + +ORACLE_PACKAGE = "tests.spec_oracles" +ORACLE_MODULES: tuple[str, ...] = ( + "v2024_11_05", + "v2025_03_26", + "v2025_06_18", + "v2025_11_25", + "v2026_07_28", + "ext_tasks", +) + +SDK_MODULES: tuple[ModuleType, ...] = (mcp.types, mcp.types.jsonrpc) + +ALLOWLIST_PATH = Path(__file__).parent / "burndown_allowlist.json" + +HARD_CHECKS = frozenset( + { + "SPEC-TYPE-MISSING", + "SPEC-FIELD-MISSING", + "SDK-REQUIRED-NOT-IN-SPEC", + "TYPE-NARROWER", + "SDK-TYPE-PHANTOM", + "SDK-FIELD-PHANTOM", + } +) +SOFT_CHECKS = frozenset({"SPEC-FIELD-OPTIONAL-IN-SDK", "TYPE-WIDER", "TYPE-INCOMPARABLE"}) +GAP_CHECKS = frozenset({"VACUOUS-SCHEMA", "REQUIRED-UNVERIFIABLE"}) +CATEGORIES = frozenset({"not-yet-implemented", "deliberate-deviation", "schema-gap", "suspected-sdk-bug"}) + +# Spec def name -> SDK attribute name: the inverse of the SDK's reviewed +# rename record (each entry's rationale is commented there). Applies to every +# oracle; NAME_MAP_BY_ORACLE overrides per oracle module. +NAME_MAP: dict[str, str] = {schema: sdk for sdk, schema in SDK_TO_SCHEMA_RENAMES.items()} +NAME_MAP_BY_ORACLE: dict[str, dict[str, str]] = { + # The 2025-06-18 schema renamed ResourceReference to ResourceTemplateReference; + # the SDK uses the new name for all versions. Version-scoped pairing like + # this stays here: SDK_TO_SCHEMA_RENAMES is one flat SDK name -> schema + # name map and cannot say "renamed at version X". + "v2024_11_05": {"ResourceReference": "ResourceTemplateReference"}, + "v2025_03_26": {"ResourceReference": "ResourceTemplateReference"}, +} + +# SDK public names exempt from SDK-TYPE-PHANTOM: machinery that has no spec +# def on purpose. Anything less obvious belongs in the allowlist instead, +# where it stays visible in the burn-down report. +SDK_MACHINERY: frozenset[str] = frozenset( + { + # Protocol version constants (schema.ts constants, not $defs). + "LATEST_PROTOCOL_VERSION", + "DEFAULT_NEGOTIATED_VERSION", + # JSON-RPC / MCP error-code int constants. + "CONNECTION_CLOSED", + "REQUEST_TIMEOUT", + "REQUEST_CANCELLED", + "PARSE_ERROR", + "INVALID_REQUEST", + "METHOD_NOT_FOUND", + "INVALID_PARAMS", + "INTERNAL_ERROR", + "URL_ELICITATION_REQUIRED", + # Module-level TypeAdapter instances. + "client_request_adapter", + "client_notification_adapter", + "client_result_adapter", + "server_request_adapter", + "server_notification_adapter", + "server_result_adapter", + "jsonrpc_message_adapter", + # TypedDict plumbing for request _meta (paired with RequestMetaObject + # via NAME_MAP; the TypedDict itself is not a def). + "RequestParamsMeta", + # Capability sub-models lifted from inline (non-$defs) schema objects. + "PromptsCapability", + "ResourcesCapability", + "RootsCapability", + "ToolsCapability", + "LoggingCapability", + "CompletionsCapability", + "SamplingCapability", + "SamplingContextCapability", + "SamplingToolsCapability", + "ElicitationCapability", + "FormElicitationCapability", + "UrlElicitationCapability", + # Models/aliases lifted from inline (non-$defs) schema objects. + "Completion", # CompleteResult.completion inline object + "CompletionArgument", # CompleteRequest.params.argument inline object + "CompletionContext", # CompleteRequest.params.context inline object + "ElicitCompleteNotificationParams", # inline params at the pinned SHA + "IconTheme", # Icon.theme inline enum + "IncludeContext", # CreateMessageRequest.params.includeContext inline enum + "StopReason", # CreateMessageResult.stopReason inline string union + } +) + +Sig = tuple[Any, ...] + +_ANY: Sig = ("any",) +_NULL: Sig = ("null",) + + +@dataclass(frozen=True) +class Finding: + """One divergence between an oracle def and the SDK's curated types.""" + + check: str + oracle: str + name: str + field: str | None + detail: str + + @property + def hard(self) -> bool: + return self.check in HARD_CHECKS + + @property + def id(self) -> str: + field_part = f".{self.field}" if self.field is not None else "" + return f"{self.oracle}/{self.name}{field_part}#{self.check}" + + +@dataclass(frozen=True) +class AllowlistEntry: + """One allowlisted finding (or schema-gap exemption).""" + + id: str + check: str + oracle: str + name: str + field: str | None + category: str + reason: str + track: str | None + + +def load_allowlist(path: Path = ALLOWLIST_PATH) -> list[AllowlistEntry]: + """Load and validate the burn-down allowlist.""" + with path.open() as f: + raw: dict[str, Any] = json.load(f) + entries: list[AllowlistEntry] = [] + for item in raw["entries"]: + entry = AllowlistEntry( + id=item["id"], + check=item["check"], + oracle=item["oracle"], + name=item["name"], + field=item.get("field"), + category=item["category"], + reason=item["reason"], + track=item.get("track"), + ) + field_part = f".{entry.field}" if entry.field is not None else "" + expected_id = f"{entry.oracle}/{entry.name}{field_part}#{entry.check}" + if entry.id != expected_id: + raise ValueError(f"allowlist entry id {entry.id!r} does not match its parts ({expected_id!r})") + if entry.category not in CATEGORIES: + raise ValueError(f"allowlist entry {entry.id!r}: unknown category {entry.category!r}") + if entry.check not in HARD_CHECKS | GAP_CHECKS: + raise ValueError(f"allowlist entry {entry.id!r}: only hard findings can be allowlisted") + if entry.check in GAP_CHECKS and entry.category != "schema-gap": + raise ValueError(f"allowlist entry {entry.id!r}: {entry.check} entries must be category schema-gap") + if entry.category == "schema-gap" and entry.check not in GAP_CHECKS: + raise ValueError(f"allowlist entry {entry.id!r}: schema-gap entries must use a gap pseudo-check") + entries.append(entry) + ids = [e.id for e in entries] + if len(ids) != len(set(ids)): + dupes = sorted({i for i in ids if ids.count(i) > 1}) + raise ValueError(f"duplicate allowlist ids: {dupes}") + return entries + + +def oracle_module(name: str) -> ModuleType: + """Import an oracle module lazily (keeps collection-time imports cheap).""" + return importlib.import_module(f"{ORACLE_PACKAGE}.{name}") + + +def resolve_sdk_name(oracle: str, def_name: str) -> str: + """Spec def name -> expected SDK attribute name.""" + by_oracle = NAME_MAP_BY_ORACLE.get(oracle, {}) + return by_oracle.get(def_name, NAME_MAP.get(def_name, def_name)) + + +def sdk_lookup(name: str) -> Any: + """Find an SDK counterpart by name, searching SDK_MODULES in order.""" + for module in SDK_MODULES: + if hasattr(module, name): + return getattr(module, name) + return None + + +_REVERSE_NAME_MAP: dict[str, str] = {sdk: spec for spec, sdk in NAME_MAP.items()} + + +def _normalize_model_name(name: str, *, sdk: bool) -> str: + """Map SDK model names back to spec names so paired models compare equal.""" + return _REVERSE_NAME_MAP.get(name, name) if sdk else name + + +def wire_fields(model: type[BaseModel]) -> dict[str, FieldInfo]: + """Map wire name (serialization alias chain) -> FieldInfo for a model.""" + fields: dict[str, FieldInfo] = {} + for field_name, info in model.model_fields.items(): + alias = info.serialization_alias or info.alias + fields[alias or field_name] = info + return fields + + +def sig(annotation: Any, *, sdk: bool, _seen: frozenset[str] = frozenset()) -> Sig: + """Canonicalize an annotation into a comparable signature tuple.""" + if annotation is None or annotation is type(None): + return _NULL + if annotation is Any: + return _ANY + if annotation == Base64Str: + return ("base64",) + if isinstance(annotation, TypeAliasType): + name = annotation.__name__ + if name in _seen: + return ("recursive", name) + return sig(annotation.__value__, sdk=sdk, _seen=_seen | {name}) + origin = get_origin(annotation) + if origin is not None and str(origin) == "typing.Annotated": + return sig(get_args(annotation)[0], sdk=sdk, _seen=_seen) + if origin is Literal: + return ("lit", frozenset(get_args(annotation))) + if origin is Union or origin is types.UnionType: + members = frozenset(sig(arg, sdk=sdk, _seen=_seen) for arg in get_args(annotation)) + if len(members) == 1: + return next(iter(members)) + return ("union", members) + if origin in (list, tuple, set, frozenset): + args = get_args(annotation) + item = sig(args[0], sdk=sdk, _seen=_seen) if args else _ANY + return ("list", item) + if origin is dict: + args = get_args(annotation) + if args: + return ("dict", sig(args[0], sdk=sdk, _seen=_seen), sig(args[1], sdk=sdk, _seen=_seen)) + return ("dict", _ANY, _ANY) + if is_typeddict(annotation): + # Open dict semantics: constraints on individual TypedDict keys are out + # of scope for the v1 algebra. + return ("dict", ("prim", "str"), _ANY) + if isinstance(annotation, type): + if issubclass(annotation, BaseModel): + return ("model", _normalize_model_name(annotation.__name__, sdk=sdk)) + if issubclass(annotation, FileUrl): + return ("url", "file") + if issubclass(annotation, AnyUrl): + return ("url", "any") + if annotation is bool: + return ("prim", "bool") + if annotation is int: + return ("prim", "int") + if annotation is float: + return ("prim", "float") + if annotation is str: + return ("prim", "str") + return ("opaque", f"{annotation.__module__}.{annotation.__qualname__}") + return ("opaque", repr(annotation)) + + +Compat = Literal["equal", "sdk_wider", "sdk_narrower", "incomparable"] + + +def _members(s: Sig) -> frozenset[Sig]: + if s[0] == "union": + members: frozenset[Sig] = s[1] + return members + return frozenset({s}) + + +def _strip_optional_null(s: Sig) -> Sig: + """Drop the `| None` optionality artifact from an optional field's signature. + + Both the generator and the SDK encode "field may be absent" as + ``X | None = ``; the null member says nothing about wire + nullability there, so comparing optional fields with it inflates noise. + Required fields keep their null members (genuine wire nullability, e.g. + JSONRPCError.id). + """ + if s[0] != "union": + return s + members = frozenset(m for m in _members(s) if m != _NULL) + if not members: + return _NULL + if len(members) == 1: + return next(iter(members)) + return ("union", members) + + +def _lit_base(values: frozenset[Any]) -> str | None: + """The primitive name shared by all values of a Literal, if any.""" + bases = {type(v).__name__ for v in values} + return next(iter(bases)) if len(bases) == 1 else None + + +def compat(spec: Sig, sdk: Sig) -> Compat: + """Relate a spec-side signature to an SDK-side signature. + + `sdk_wider` means the SDK accepts everything the spec accepts (and more); + `sdk_narrower` means the SDK provably rejects spec-valid values. Only + `sdk_narrower` becomes a hard finding. + """ + if spec == sdk: + return "equal" + if sdk == _ANY: + return "sdk_wider" + if spec == _ANY: + return "sdk_narrower" + spec_members = _members(spec) + sdk_members = _members(sdk) + if len(spec_members) > 1 or len(sdk_members) > 1: + saw_incomparable = False + all_accepted = True + for spec_member in spec_members: + results = [compat(spec_member, sdk_member) for sdk_member in sdk_members] + if any(r in ("equal", "sdk_wider") for r in results): + continue + all_accepted = False + if any(r == "incomparable" for r in results): + saw_incomparable = True + if all_accepted: + return "sdk_wider" + return "incomparable" if saw_incomparable else "sdk_narrower" + return _compat_single(spec, sdk) + + +def _compat_single(spec: Sig, sdk: Sig) -> Compat: + """compat() for two non-union signatures that are not equal and not Any.""" + spec_kind, sdk_kind = spec[0], sdk[0] + if spec_kind == "lit" and sdk_kind == "lit": + spec_values: frozenset[Any] = spec[1] + sdk_values: frozenset[Any] = sdk[1] + if spec_values <= sdk_values: + return "sdk_wider" + if sdk_values < spec_values: + return "sdk_narrower" + return "incomparable" + if spec_kind == "lit" and sdk_kind == "prim": + return "sdk_wider" if _lit_base(spec[1]) == sdk[1] else "incomparable" + if spec_kind == "prim" and sdk_kind == "lit": + return "sdk_narrower" if _lit_base(sdk[1]) == spec[1] else "incomparable" + if spec_kind == "prim" and sdk_kind == "prim": + if spec[1] == "int" and sdk[1] == "float": + return "sdk_wider" + if spec[1] == "float" and sdk[1] == "int": + return "sdk_narrower" + return "incomparable" + if spec_kind == "base64": + if sdk == ("prim", "str"): + return "sdk_wider" + return "incomparable" + if sdk_kind == "base64": + if spec == ("prim", "str"): + return "sdk_narrower" + return "incomparable" + if spec_kind == "url" and sdk_kind == "url": + # Not equal, so one side is file-only: AnyUrl accepts more than FileUrl. + return "sdk_narrower" if sdk[1] == "file" else "sdk_wider" + if spec_kind == "url" and sdk == ("prim", "str"): + return "sdk_wider" + if spec == ("prim", "str") and sdk_kind == "url": + return "sdk_narrower" + if spec_kind == "list" and sdk_kind == "list": + return compat(spec[1], sdk[1]) + if spec_kind == "dict" and sdk_kind == "dict": + key = compat(spec[1], sdk[1]) + value = compat(spec[2], sdk[2]) + ranking = {"incomparable": 3, "sdk_narrower": 2, "sdk_wider": 1, "equal": 0} + worst = key if ranking[key] >= ranking[value] else value + return worst + # model-vs-model with different names, model-vs-non-model, opaque, + # recursive markers with different names: the algebra cannot relate them. + return "incomparable" + + +def _is_model(obj: Any) -> bool: + return isinstance(obj, type) and issubclass(obj, BaseModel) + + +GapPaths = frozenset[tuple[str, str, str | None]] + + +def gap_paths(entries: list[AllowlistEntry]) -> GapPaths: + """(oracle, def, field-or-None) paths whose type/requiredness checks are skipped.""" + return frozenset((e.oracle, e.name, e.field) for e in entries if e.category == "schema-gap") + + +def compare_oracle(oracle: str, gaps: GapPaths = frozenset()) -> list[Finding]: + """Run the per-oracle checks for one oracle module.""" + module = oracle_module(oracle) + findings: list[Finding] = [] + spec_defs: tuple[str, ...] = module.SPEC_DEFS + for def_name in spec_defs: + spec_obj = getattr(module, def_name) + sdk_name = resolve_sdk_name(oracle, def_name) + sdk_obj = sdk_lookup(sdk_name) + if sdk_obj is None: + findings.append( + Finding( + check="SPEC-TYPE-MISSING", + oracle=oracle, + name=def_name, + field=None, + detail=f"no SDK counterpart named {sdk_name!r}", + ) + ) + continue + if _is_model(spec_obj) and _is_model(sdk_obj): + findings.extend(_compare_models(oracle, def_name, spec_obj, sdk_obj, gaps)) + elif (oracle, def_name, None) not in gaps: + spec_sig = sig(spec_obj, sdk=False) + sdk_sig = sig(sdk_obj, sdk=True) + findings.extend(_type_finding(oracle, def_name, None, spec_sig, sdk_sig)) + findings.sort(key=lambda f: (f.name, f.field or "", f.check)) + return findings + + +def _type_finding(oracle: str, name: str, field: str | None, spec_sig: Sig, sdk_sig: Sig) -> list[Finding]: + relation = compat(spec_sig, sdk_sig) + if relation == "equal": + return [] + check = { + "sdk_narrower": "TYPE-NARROWER", + "sdk_wider": "TYPE-WIDER", + "incomparable": "TYPE-INCOMPARABLE", + }[relation] + return [Finding(check=check, oracle=oracle, name=name, field=field, detail=f"spec={spec_sig} sdk={sdk_sig}")] + + +def _compare_models( + oracle: str, + def_name: str, + spec_model: type[BaseModel], + sdk_model: type[BaseModel], + gaps: GapPaths, +) -> list[Finding]: + findings: list[Finding] = [] + spec_fields = wire_fields(spec_model) + sdk_fields = wire_fields(sdk_model) + for wire_name, spec_info in spec_fields.items(): + if wire_name not in sdk_fields: + findings.append( + Finding( + check="SPEC-FIELD-MISSING", + oracle=oracle, + name=def_name, + field=wire_name, + detail=f"oracle field ({'required' if spec_info.is_required() else 'optional'}) absent on SDK " + f"{sdk_model.__name__}", + ) + ) + continue + if (oracle, def_name, wire_name) in gaps: + continue + sdk_info = sdk_fields[wire_name] + if sdk_info.is_required() and not spec_info.is_required(): + findings.append( + Finding( + check="SDK-REQUIRED-NOT-IN-SPEC", + oracle=oracle, + name=def_name, + field=wire_name, + detail="SDK requires this field; the oracle has it optional", + ) + ) + elif spec_info.is_required() and not sdk_info.is_required(): + findings.append( + Finding( + check="SPEC-FIELD-OPTIONAL-IN-SDK", + oracle=oracle, + name=def_name, + field=wire_name, + detail="oracle requires this field; the SDK has it optional", + ) + ) + spec_sig = sig(spec_info.annotation, sdk=False) + sdk_sig = sig(sdk_info.annotation, sdk=True) + if not spec_info.is_required(): + spec_sig = _strip_optional_null(spec_sig) + if not sdk_info.is_required(): + sdk_sig = _strip_optional_null(sdk_sig) + findings.extend(_type_finding(oracle, def_name, wire_name, spec_sig, sdk_sig)) + for wire_name, sdk_info in sdk_fields.items(): + if wire_name in spec_fields or (oracle, def_name, wire_name) in gaps: + continue + if sdk_info.is_required(): + findings.append( + Finding( + check="SDK-REQUIRED-NOT-IN-SPEC", + oracle=oracle, + name=def_name, + field=wire_name, + detail="SDK requires this field; this oracle version does not have it at all", + ) + ) + return findings + + +def _sdk_public_names() -> list[str]: + names: list[str] = list(mcp.types.__all__) + return names + + +def _sdk_to_oracle_defs() -> dict[str, list[tuple[str, str]]]: + """SDK attribute name -> [(oracle, def name)] for every def in every oracle.""" + pairing: dict[str, list[tuple[str, str]]] = {} + for oracle in ORACLE_MODULES: + module = oracle_module(oracle) + spec_defs: tuple[str, ...] = module.SPEC_DEFS + for def_name in spec_defs: + sdk_name = resolve_sdk_name(oracle, def_name) + pairing.setdefault(sdk_name, []).append((oracle, def_name)) + return pairing + + +def aggregated_findings() -> list[Finding]: + """SDK-TYPE-PHANTOM and SDK-FIELD-PHANTOM, run once across all oracles.""" + findings: list[Finding] = [] + pairing = _sdk_to_oracle_defs() + for sdk_name in _sdk_public_names(): + if sdk_name in SDK_MACHINERY: + continue + paired = pairing.get(sdk_name) + if not paired: + findings.append( + Finding( + check="SDK-TYPE-PHANTOM", + oracle="sdk", + name=sdk_name, + field=None, + detail="SDK public type maps to no def in any oracle", + ) + ) + continue + sdk_obj = sdk_lookup(sdk_name) + if not _is_model(sdk_obj): + continue + oracle_wire_names: set[str] = set() + paired_models = False + for oracle, def_name in paired: + spec_obj = getattr(oracle_module(oracle), def_name) + if _is_model(spec_obj): + paired_models = True + oracle_wire_names.update(wire_fields(spec_obj)) + if not paired_models: + continue + paired_defs = sorted({def_name for _, def_name in paired}) + findings.extend( + Finding( + check="SDK-FIELD-PHANTOM", + oracle="sdk", + name=sdk_name, + field=wire_name, + detail=f"SDK field's wire name appears in no oracle version of this def (paired: {paired_defs})", + ) + for wire_name in wire_fields(sdk_obj) + if wire_name not in oracle_wire_names + ) + findings.sort(key=lambda f: (f.name, f.field or "", f.check)) + return findings + + +def all_findings(gaps: GapPaths = frozenset()) -> list[Finding]: + """Every finding: per-oracle checks for each oracle plus the aggregated ones.""" + findings: list[Finding] = [] + for oracle in ORACLE_MODULES: + findings.extend(compare_oracle(oracle, gaps)) + findings.extend(aggregated_findings()) + return findings + + +def schema_gap_applies(entry: AllowlistEntry) -> bool: + """Whether a schema-gap exemption still matches the generated oracle. + + When a future ext-tasks pin repairs the schema (real `$ref`s, restored + `required` arrays), regeneration changes the oracle, this returns False, + the entry is stale, and the gate fails until it is removed. + + - VACUOUS-SCHEMA with no field: the def itself resolves to a signature + containing `Any` (e.g. ``InputRequest: TypeAlias = Any``). + - VACUOUS-SCHEMA with a field: somewhere in the def's model closure + (the def plus its synthetic nested models, e.g. union variants and + params objects) a field with that wire name has a signature containing + `Any`. The closure is searched per wire name so unrelated open sites + (``_meta``, completed-task ``result``) cannot keep the entry alive. + - REQUIRED-UNVERIFIABLE: the def is a model with no required fields at + all (its `required` array / intersection half was lost in the schema). + """ + module = oracle_module(entry.oracle) + obj = getattr(module, entry.name, None) + if obj is None: + return False + if entry.check == "VACUOUS-SCHEMA": + if entry.field is None: + return not _is_model(obj) and _contains_any(sig(obj, sdk=False)) + return _closure_field_has_any(obj, entry.field) + if entry.check == "REQUIRED-UNVERIFIABLE": + if not _is_model(obj): + return False + return all(not info.is_required() for info in obj.model_fields.values()) + raise ValueError(f"not a schema-gap pseudo-check: {entry.check}") + + +def _closure_models(annotation: Any, seen: set[type[BaseModel]]) -> None: + """Collect every pydantic model reachable from an annotation into `seen`.""" + if _is_model(annotation): + model: type[BaseModel] = annotation + if model in seen: + return + seen.add(model) + for info in model.model_fields.values(): + _closure_models(info.annotation, seen) + return + if isinstance(annotation, TypeAliasType): + _closure_models(annotation.__value__, seen) + return + for arg in get_args(annotation): + _closure_models(arg, seen) + + +def _closure_field_has_any(obj: Any, wire_name: str) -> bool: + seen: set[type[BaseModel]] = set() + _closure_models(obj, seen) + for model in seen: + info = wire_fields(model).get(wire_name) + if info is not None and _contains_any(sig(info.annotation, sdk=False)): + return True + return False + + +def _contains_any(s: Sig) -> bool: + if s == _ANY: + return True + if s[0] in ("union",): + return any(_contains_any(member) for member in s[1]) + if s[0] == "list": + return _contains_any(s[1]) + if s[0] == "dict": + return _contains_any(s[1]) or _contains_any(s[2]) + return False + + +@dataclass(frozen=True) +class Evaluation: + """Outcome of matching findings against the allowlist.""" + + new_hard: tuple[Finding, ...] + stale_entries: tuple[AllowlistEntry, ...] + allowlisted_hard: tuple[Finding, ...] + soft: tuple[Finding, ...] + + +def evaluate(findings: list[Finding], entries: list[AllowlistEntry]) -> Evaluation: + """Match findings against allowlist entries (both ratchet directions). + + A hard finding without a matching entry is new (gate fails). A + non-schema-gap entry whose id matches no finding is stale (gate fails). + Schema-gap entries never match findings; their staleness is decided by + `schema_gap_applies`. + """ + allowed_ids = {e.id for e in entries if e.category != "schema-gap"} + finding_ids = {f.id for f in findings} + new_hard = tuple(f for f in findings if f.hard and f.id not in allowed_ids) + allowlisted_hard = tuple(f for f in findings if f.hard and f.id in allowed_ids) + stale = tuple(entry for entry in entries if _entry_is_stale(entry, finding_ids)) + soft = tuple(f for f in findings if not f.hard) + return Evaluation( + new_hard=new_hard, + stale_entries=stale, + allowlisted_hard=allowlisted_hard, + soft=soft, + ) + + +def _entry_is_stale(entry: AllowlistEntry, finding_ids: set[str]) -> bool: + if entry.category == "schema-gap": + return not schema_gap_applies(entry) + return entry.id not in finding_ids diff --git a/tests/spec_oracles/burndown_allowlist.json b/tests/spec_oracles/burndown_allowlist.json new file mode 100644 index 0000000000..123cfaf554 --- /dev/null +++ b/tests/spec_oracles/burndown_allowlist.json @@ -0,0 +1,2344 @@ +{ + "entries": [ + { + "id": "ext_tasks/CancelTaskRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "CancelTaskRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "ext_tasks/CancelTaskRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "CancelTaskRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "ext_tasks/CancelTaskResult#REQUIRED-UNVERIFIABLE", + "check": "REQUIRED-UNVERIFIABLE", + "oracle": "ext_tasks", + "name": "CancelTaskResult", + "field": null, + "category": "schema-gap", + "reason": "ext-tasks schema.json lost the Task half of the CancelTaskResult intersection (Result & Task) at the ts-to-zod boundary: the def has only the optional _meta property and no required array, so field presence and requiredness are unverifiable. (CreateTaskResult and GetTaskResult survived with their required arrays intact, so only this def is exempted.)", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CancelTaskResult.createdAt#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "ext_tasks", + "name": "CancelTaskResult", + "field": "createdAt", + "category": "deliberate-deviation", + "reason": "ext-tasks schema.json lost the Task half of the CancelTaskResult intersection (Result & Task) at the ts-to-zod boundary, so the def carries only _meta; the SDK models the 2025-11-25 core def, which requires the task fields. See the REQUIRED-UNVERIFIABLE exemption for this def.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CancelTaskResult.lastUpdatedAt#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "ext_tasks", + "name": "CancelTaskResult", + "field": "lastUpdatedAt", + "category": "deliberate-deviation", + "reason": "ext-tasks schema.json lost the Task half of the CancelTaskResult intersection (Result & Task) at the ts-to-zod boundary, so the def carries only _meta; the SDK models the 2025-11-25 core def, which requires the task fields. See the REQUIRED-UNVERIFIABLE exemption for this def.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CancelTaskResult.status#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "ext_tasks", + "name": "CancelTaskResult", + "field": "status", + "category": "deliberate-deviation", + "reason": "ext-tasks schema.json lost the Task half of the CancelTaskResult intersection (Result & Task) at the ts-to-zod boundary, so the def carries only _meta; the SDK models the 2025-11-25 core def, which requires the task fields. See the REQUIRED-UNVERIFIABLE exemption for this def.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CancelTaskResult.taskId#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "ext_tasks", + "name": "CancelTaskResult", + "field": "taskId", + "category": "deliberate-deviation", + "reason": "ext-tasks schema.json lost the Task half of the CancelTaskResult intersection (Result & Task) at the ts-to-zod boundary, so the def carries only _meta; the SDK models the 2025-11-25 core def, which requires the task fields. See the REQUIRED-UNVERIFIABLE exemption for this def.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CancelTaskResult.ttl#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "ext_tasks", + "name": "CancelTaskResult", + "field": "ttl", + "category": "deliberate-deviation", + "reason": "ext-tasks schema.json lost the Task half of the CancelTaskResult intersection (Result & Task) at the ts-to-zod boundary, so the def carries only _meta; the SDK models the 2025-11-25 core def, which requires the task fields. See the REQUIRED-UNVERIFIABLE exemption for this def.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CancelledTask#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "CancelledTask", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CompletedTask#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "CompletedTask", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CreateTaskResult.createdAt#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "CreateTaskResult", + "field": "createdAt", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CreateTaskResult.lastUpdatedAt#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "CreateTaskResult", + "field": "lastUpdatedAt", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CreateTaskResult.pollIntervalMs#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "CreateTaskResult", + "field": "pollIntervalMs", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CreateTaskResult.status#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "CreateTaskResult", + "field": "status", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CreateTaskResult.statusMessage#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "CreateTaskResult", + "field": "statusMessage", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CreateTaskResult.task#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "ext_tasks", + "name": "CreateTaskResult", + "field": "task", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CreateTaskResult.taskId#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "CreateTaskResult", + "field": "taskId", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/CreateTaskResult.ttlMs#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "CreateTaskResult", + "field": "ttlMs", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/DetailedTask#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "DetailedTask", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/DetailedTask.inputRequests#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "DetailedTask", + "field": "inputRequests", + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/FailedTask#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "FailedTask", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/GetTaskRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "GetTaskRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "ext_tasks/GetTaskRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "GetTaskRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "ext_tasks/GetTaskResult.inputRequests#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "GetTaskResult", + "field": "inputRequests", + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/InputRequest#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "InputRequest", + "field": null, + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/InputRequests#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "InputRequests", + "field": null, + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/InputRequiredTask#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "InputRequiredTask", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/InputRequiredTask.inputRequests#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "InputRequiredTask", + "field": "inputRequests", + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/InputResponse#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "InputResponse", + "field": null, + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/InputResponses#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "InputResponses", + "field": null, + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/Task.pollIntervalMs#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "Task", + "field": "pollIntervalMs", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/Task.ttl#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "ext_tasks", + "name": "Task", + "field": "ttl", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/Task.ttlMs#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "Task", + "field": "ttlMs", + "category": "deliberate-deviation", + "reason": "The SDK restores the 2025-11-25 core schema's tasks types; the experimental tasks-extension schema revises these defs (flattened result fields, ttl renamed to ttlMs, added pollIntervalMs/statusMessage). The extension itself is not implemented in the SDK.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/TaskStatusNotification.inputRequests#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "TaskStatusNotification", + "field": "inputRequests", + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/TaskStatusNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "ext_tasks", + "name": "TaskStatusNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "ext_tasks/TaskStatusNotificationParams.inputRequests#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "TaskStatusNotificationParams", + "field": "inputRequests", + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/TaskSubscriptionAcknowledgedNotifications#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "TaskSubscriptionAcknowledgedNotifications", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/TaskSubscriptionNotifications#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "TaskSubscriptionNotifications", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/TasksExtensionCapability#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "TasksExtensionCapability", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/UpdateTaskRequest#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "UpdateTaskRequest", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/UpdateTaskRequest.inputResponses#VACUOUS-SCHEMA", + "check": "VACUOUS-SCHEMA", + "oracle": "ext_tasks", + "name": "UpdateTaskRequest", + "field": "inputResponses", + "category": "schema-gap", + "reason": "ext-tasks schema.json lost every core $ref at the ts-to-zod import boundary (vacuous anyOf:[{},{},{}] collapsed to Any by the generator); followup-1 section 2.3, re-verified at pin dd47977f. The intended type is the InputRequest/InputResponse union of core draft types.", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/UpdateTaskResult#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "UpdateTaskResult", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "ext_tasks/WorkingTask#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "ext_tasks", + "name": "WorkingTask", + "field": null, + "category": "not-yet-implemented", + "reason": "tasks extension (experimental-ext-tasks) is not implemented in the SDK", + "track": "tasks-ext" + }, + { + "id": "sdk/CLIENT_CAPABILITIES_META_KEY#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "CLIENT_CAPABILITIES_META_KEY", + "field": null, + "category": "deliberate-deviation", + "reason": "Named spec constant, not a schema $defs entry; the generated oracles model defs only.", + "track": "lifecycle-draft" + }, + { + "id": "sdk/CLIENT_INFO_META_KEY#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "CLIENT_INFO_META_KEY", + "field": null, + "category": "deliberate-deviation", + "reason": "Named spec constant, not a schema $defs entry; the generated oracles model defs only.", + "track": "lifecycle-draft" + }, + { + "id": "sdk/CancelTaskRequestParams#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "CancelTaskRequestParams", + "field": null, + "category": "deliberate-deviation", + "reason": "Inline params object lifted to a model; the schema defines these params inline on the request def.", + "track": "tasks-legacy" + }, + { + "id": "sdk/CancelTaskResult.resultType#SDK-FIELD-PHANTOM", + "check": "SDK-FIELD-PHANTOM", + "oracle": "sdk", + "name": "CancelTaskResult", + "field": "resultType", + "category": "deliberate-deviation", + "reason": "resultType (added in 2026-07-28; absent means complete) is defined once on the SDK's shared Result base, so every result class carries the field; this def's last schema revision predates 2026-07-28, so no oracle version of the paired def has it.", + "track": "envelope" + }, + { + "id": "sdk/CreateMessageResult.resultType#SDK-FIELD-PHANTOM", + "check": "SDK-FIELD-PHANTOM", + "oracle": "sdk", + "name": "CreateMessageResult", + "field": "resultType", + "category": "deliberate-deviation", + "reason": "resultType (added in 2026-07-28; absent means complete) is defined once on the SDK's shared Result base, so every result class carries the field; this def's last schema revision predates 2026-07-28, so no oracle version of the paired def has it.", + "track": "envelope" + }, + { + "id": "sdk/CreateMessageResultWithTools#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "CreateMessageResultWithTools", + "field": null, + "category": "deliberate-deviation", + "reason": "SDK splits the spec's CreateMessageResult into a backwards-compatible single-content result and a with-tools variant supporting array content; the spec has one def.", + "track": "sampling" + }, + { + "id": "sdk/CreateTaskResult.resultType#SDK-FIELD-PHANTOM", + "check": "SDK-FIELD-PHANTOM", + "oracle": "sdk", + "name": "CreateTaskResult", + "field": "resultType", + "category": "deliberate-deviation", + "reason": "resultType (added in 2026-07-28; absent means complete) is defined once on the SDK's shared Result base, so every result class carries the field; this def's last schema revision predates 2026-07-28, so no oracle version of the paired def has it.", + "track": "envelope" + }, + { + "id": "sdk/ElicitRequestedSchema#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "ElicitRequestedSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "sdk/ElicitResult.resultType#SDK-FIELD-PHANTOM", + "check": "SDK-FIELD-PHANTOM", + "oracle": "sdk", + "name": "ElicitResult", + "field": "resultType", + "category": "deliberate-deviation", + "reason": "resultType (added in 2026-07-28; absent means complete) is defined once on the SDK's shared Result base, so every result class carries the field; this def's last schema revision predates 2026-07-28, so no oracle version of the paired def has it.", + "track": "envelope" + }, + { + "id": "sdk/ElicitationRequiredErrorData#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "ElicitationRequiredErrorData", + "field": null, + "category": "deliberate-deviation", + "reason": "SDK-curated shape for the URLElicitationRequiredError error.data payload; the 2025-11-25 schema models it inline on the named error (which the SDK has not implemented as a type - see URLElicitationRequiredError entry).", + "track": "errors" + }, + { + "id": "sdk/GetTaskPayloadRequestParams#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "GetTaskPayloadRequestParams", + "field": null, + "category": "deliberate-deviation", + "reason": "Inline params object lifted to a model; the schema defines these params inline on the request def.", + "track": "tasks-legacy" + }, + { + "id": "sdk/GetTaskPayloadResult.resultType#SDK-FIELD-PHANTOM", + "check": "SDK-FIELD-PHANTOM", + "oracle": "sdk", + "name": "GetTaskPayloadResult", + "field": "resultType", + "category": "deliberate-deviation", + "reason": "resultType (added in 2026-07-28; absent means complete) is defined once on the SDK's shared Result base, so every result class carries the field; this def's last schema revision predates 2026-07-28, so no oracle version of the paired def has it.", + "track": "envelope" + }, + { + "id": "sdk/GetTaskRequestParams#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "GetTaskRequestParams", + "field": null, + "category": "deliberate-deviation", + "reason": "Inline params object lifted to a model; the schema defines these params inline on the request def.", + "track": "tasks-legacy" + }, + { + "id": "sdk/GetTaskResult.resultType#SDK-FIELD-PHANTOM", + "check": "SDK-FIELD-PHANTOM", + "oracle": "sdk", + "name": "GetTaskResult", + "field": "resultType", + "category": "deliberate-deviation", + "reason": "resultType (added in 2026-07-28; absent means complete) is defined once on the SDK's shared Result base, so every result class carries the field; this def's last schema revision predates 2026-07-28, so no oracle version of the paired def has it.", + "track": "envelope" + }, + { + "id": "sdk/InitializeResult.resultType#SDK-FIELD-PHANTOM", + "check": "SDK-FIELD-PHANTOM", + "oracle": "sdk", + "name": "InitializeResult", + "field": "resultType", + "category": "deliberate-deviation", + "reason": "resultType (added in 2026-07-28; absent means complete) is defined once on the SDK's shared Result base, so every result class carries the field; this def's last schema revision predates 2026-07-28, so no oracle version of the paired def has it.", + "track": "envelope" + }, + { + "id": "sdk/JSONRPC_VERSION#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "JSONRPC_VERSION", + "field": null, + "category": "deliberate-deviation", + "reason": "Named spec constant, not a schema $defs entry; the generated oracles model defs only.", + "track": "envelope" + }, + { + "id": "sdk/LOG_LEVEL_META_KEY#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "LOG_LEVEL_META_KEY", + "field": null, + "category": "deliberate-deviation", + "reason": "Named spec constant, not a schema $defs entry; the generated oracles model defs only.", + "track": "lifecycle-draft" + }, + { + "id": "sdk/ListRootsResult.resultType#SDK-FIELD-PHANTOM", + "check": "SDK-FIELD-PHANTOM", + "oracle": "sdk", + "name": "ListRootsResult", + "field": "resultType", + "category": "deliberate-deviation", + "reason": "resultType (added in 2026-07-28; absent means complete) is defined once on the SDK's shared Result base, so every result class carries the field; this def's last schema revision predates 2026-07-28, so no oracle version of the paired def has it.", + "track": "envelope" + }, + { + "id": "sdk/ListTasksResult.resultType#SDK-FIELD-PHANTOM", + "check": "SDK-FIELD-PHANTOM", + "oracle": "sdk", + "name": "ListTasksResult", + "field": "resultType", + "category": "deliberate-deviation", + "reason": "resultType (added in 2026-07-28; absent means complete) is defined once on the SDK's shared Result base, so every result class carries the field; this def's last schema revision predates 2026-07-28, so no oracle version of the paired def has it.", + "track": "envelope" + }, + { + "id": "sdk/MISSING_REQUIRED_CLIENT_CAPABILITY#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "MISSING_REQUIRED_CLIENT_CAPABILITY", + "field": null, + "category": "deliberate-deviation", + "reason": "Named spec constant, not a schema $defs entry; the generated oracles model defs only.", + "track": "errors" + }, + { + "id": "sdk/MissingRequiredClientCapabilityErrorData#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "MissingRequiredClientCapabilityErrorData", + "field": null, + "category": "deliberate-deviation", + "reason": "SDK-curated shape for a named JSON-RPC error's data payload (error codes added in 2026-07-28); the schema models the data inline on the named error def, which the SDK represents as an error-code constant plus this data model.", + "track": "errors" + }, + { + "id": "sdk/PROTOCOL_VERSION_META_KEY#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "PROTOCOL_VERSION_META_KEY", + "field": null, + "category": "deliberate-deviation", + "reason": "Named spec constant, not a schema $defs entry; the generated oracles model defs only.", + "track": "lifecycle-draft" + }, + { + "id": "sdk/SamplingContent#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "SamplingContent", + "field": null, + "category": "deliberate-deviation", + "reason": "SDK-curated narrowing union (text/image/audio) used by the backwards-compatible CreateMessageResult; the spec models sampling result content inline.", + "track": "sampling" + }, + { + "id": "sdk/UNSUPPORTED_PROTOCOL_VERSION#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "UNSUPPORTED_PROTOCOL_VERSION", + "field": null, + "category": "deliberate-deviation", + "reason": "Named spec constant, not a schema $defs entry; the generated oracles model defs only.", + "track": "errors" + }, + { + "id": "sdk/UnsupportedProtocolVersionErrorData#SDK-TYPE-PHANTOM", + "check": "SDK-TYPE-PHANTOM", + "oracle": "sdk", + "name": "UnsupportedProtocolVersionErrorData", + "field": null, + "category": "deliberate-deviation", + "reason": "SDK-curated shape for a named JSON-RPC error's data payload (error codes added in 2026-07-28); the schema models the data inline on the named error def, which the SDK represents as an error-code constant plus this data model.", + "track": "errors" + }, + { + "id": "v2024_11_05/Cursor#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2024_11_05", + "name": "Cursor", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK inlines cursors as plain str fields (cursor/nextCursor) instead of exporting the Cursor alias.", + "track": "pagination-caching" + }, + { + "id": "v2024_11_05/Notification.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2024_11_05", + "name": "Notification", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2024_11_05/Request.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2024_11_05", + "name": "Request", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2024_11_05/Root.uri#TYPE-NARROWER", + "check": "TYPE-NARROWER", + "oracle": "v2024_11_05", + "name": "Root", + "field": "uri", + "category": "deliberate-deviation", + "reason": "SDK uses FileUrl where the schema says format:uri; the spec description for Root.uri states it MUST start with file:// for now, so the SDK encodes the prose constraint in the type.", + "track": "roots" + }, + { + "id": "v2025_03_26/Cursor#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_03_26", + "name": "Cursor", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK inlines cursors as plain str fields (cursor/nextCursor) instead of exporting the Cursor alias.", + "track": "pagination-caching" + }, + { + "id": "v2025_03_26/JSONRPCBatchRequest#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_03_26", + "name": "JSONRPCBatchRequest", + "field": null, + "category": "deliberate-deviation", + "reason": "JSON-RPC batching existed only in the 2025-03-26 schema (removed in 2025-06-18); the SDK never implemented it and the spec dropped it.", + "track": "envelope" + }, + { + "id": "v2025_03_26/JSONRPCBatchResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_03_26", + "name": "JSONRPCBatchResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "JSON-RPC batching existed only in the 2025-03-26 schema (removed in 2025-06-18); the SDK never implemented it and the spec dropped it.", + "track": "envelope" + }, + { + "id": "v2025_03_26/Notification.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2025_03_26", + "name": "Notification", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2025_03_26/Request.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2025_03_26", + "name": "Request", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2025_03_26/Root.uri#TYPE-NARROWER", + "check": "TYPE-NARROWER", + "oracle": "v2025_03_26", + "name": "Root", + "field": "uri", + "category": "deliberate-deviation", + "reason": "SDK uses FileUrl where the schema says format:uri; the spec description for Root.uri states it MUST start with file:// for now, so the SDK encodes the prose constraint in the type.", + "track": "roots" + }, + { + "id": "v2025_06_18/Annotations.lastModified#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_06_18", + "name": "Annotations", + "field": "lastModified", + "category": "deliberate-deviation", + "reason": "Annotations.lastModified (since 2025-06-18) is deliberately omitted for now: an existing listing snapshot in tests/interaction/lowlevel/test_resources.py pins the field being dropped, and existing test expectations are frozen on this branch; the field lands together with that snapshot update.", + "track": "content" + }, + { + "id": "v2025_06_18/BooleanSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_06_18", + "name": "BooleanSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_06_18/Cursor#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_06_18", + "name": "Cursor", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK inlines cursors as plain str fields (cursor/nextCursor) instead of exporting the Cursor alias.", + "track": "pagination-caching" + }, + { + "id": "v2025_06_18/EnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_06_18", + "name": "EnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_06_18/Notification.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2025_06_18", + "name": "Notification", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2025_06_18/NumberSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_06_18", + "name": "NumberSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_06_18/PrimitiveSchemaDefinition#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_06_18", + "name": "PrimitiveSchemaDefinition", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_06_18/Request.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2025_06_18", + "name": "Request", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2025_06_18/Root.uri#TYPE-NARROWER", + "check": "TYPE-NARROWER", + "oracle": "v2025_06_18", + "name": "Root", + "field": "uri", + "category": "deliberate-deviation", + "reason": "SDK uses FileUrl where the schema says format:uri; the spec description for Root.uri states it MUST start with file:// for now, so the SDK encodes the prose constraint in the type.", + "track": "roots" + }, + { + "id": "v2025_06_18/StringSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_06_18", + "name": "StringSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/Annotations.lastModified#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "Annotations", + "field": "lastModified", + "category": "deliberate-deviation", + "reason": "Annotations.lastModified (since 2025-06-18) is deliberately omitted for now: an existing listing snapshot in tests/interaction/lowlevel/test_resources.py pins the field being dropped, and existing test expectations are frozen on this branch; the field lands together with that snapshot update.", + "track": "content" + }, + { + "id": "v2025_11_25/BooleanSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "BooleanSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/CallToolRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "CallToolRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/CallToolRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "CallToolRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/CancelTaskRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "CancelTaskRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/CancelTaskRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "CancelTaskRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/CancelledNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "CancelledNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/CompleteRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "CompleteRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/CompleteRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "CompleteRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/CreateMessageRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "CreateMessageRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/CreateMessageRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "CreateMessageRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/Cursor#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "Cursor", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK inlines cursors as plain str fields (cursor/nextCursor) instead of exporting the Cursor alias.", + "track": "pagination-caching" + }, + { + "id": "v2025_11_25/ElicitRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ElicitRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ElicitRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ElicitRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ElicitationCompleteNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ElicitationCompleteNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/EnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "EnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/GetPromptRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "GetPromptRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/GetPromptRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "GetPromptRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/GetTaskPayloadRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "GetTaskPayloadRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/GetTaskPayloadRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "GetTaskPayloadRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/GetTaskRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "GetTaskRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/GetTaskRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "GetTaskRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/Icons#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "Icons", + "field": null, + "category": "deliberate-deviation", + "reason": "Icons is an interface mixin ({icons: Icon[]}); the SDK declares the icons field directly on each type that has one instead of exporting the mixin.", + "track": "metadata-icons" + }, + { + "id": "v2025_11_25/InitializeRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "InitializeRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/InitializeRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "InitializeRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/InitializedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "InitializedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/JSONRPCErrorResponse.id#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2025_11_25", + "name": "JSONRPCErrorResponse", + "field": "id", + "category": "suspected-sdk-bug", + "reason": "SDK requires the id key on error responses; the 2025-11-25+ schemas list only error/jsonrpc as required. JSON-RPC 2.0 requires id on every response (null when no request id could be determined) and every earlier schema version agrees, so the optional id looks like a spec-side regression; the SDK keeps the required-but-nullable id until the spec settles it.", + "track": "envelope" + }, + { + "id": "v2025_11_25/LegacyTitledEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "LegacyTitledEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/ListPromptsRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListPromptsRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListPromptsRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListPromptsRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListResourceTemplatesRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListResourceTemplatesRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListResourceTemplatesRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListResourceTemplatesRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListResourcesRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListResourcesRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListResourcesRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListResourcesRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListRootsRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListRootsRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListRootsRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListRootsRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListTasksRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListTasksRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListTasksRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListTasksRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListToolsRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListToolsRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ListToolsRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ListToolsRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/LoggingMessageNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "LoggingMessageNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/MultiSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "MultiSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/Notification.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2025_11_25", + "name": "Notification", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2025_11_25/NumberSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "NumberSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/PaginatedRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "PaginatedRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/PaginatedRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "PaginatedRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/PingRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "PingRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/PingRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "PingRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/PrimitiveSchemaDefinition#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "PrimitiveSchemaDefinition", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/ProgressNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ProgressNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/PromptListChangedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "PromptListChangedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ReadResourceRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ReadResourceRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ReadResourceRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ReadResourceRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/Request.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2025_11_25", + "name": "Request", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ResourceListChangedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ResourceListChangedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/ResourceRequestParams#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "ResourceRequestParams", + "field": null, + "category": "deliberate-deviation", + "reason": "Base interface for resource-request params; the SDK flattens the uri field into each concrete params class instead of exporting the base.", + "track": "resources" + }, + { + "id": "v2025_11_25/ResourceUpdatedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ResourceUpdatedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/Root.uri#TYPE-NARROWER", + "check": "TYPE-NARROWER", + "oracle": "v2025_11_25", + "name": "Root", + "field": "uri", + "category": "deliberate-deviation", + "reason": "SDK uses FileUrl where the schema says format:uri; the spec description for Root.uri states it MUST start with file:// for now, so the SDK encodes the prose constraint in the type.", + "track": "roots" + }, + { + "id": "v2025_11_25/RootsListChangedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "RootsListChangedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/SetLevelRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "SetLevelRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/SetLevelRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "SetLevelRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/SingleSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "SingleSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/StringSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "StringSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/SubscribeRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "SubscribeRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/SubscribeRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "SubscribeRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/TaskAugmentedRequestParams#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "TaskAugmentedRequestParams", + "field": null, + "category": "deliberate-deviation", + "reason": "Shared base interface for the 2025-11-25 task-augmentable params classes; the SDK declares the task field directly on each of the four host classes (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "tasks-legacy" + }, + { + "id": "v2025_11_25/TaskStatusNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "TaskStatusNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/TitledMultiSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "TitledMultiSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/TitledSingleSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "TitledSingleSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/ToolListChangedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "ToolListChangedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/URLElicitationRequiredError#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "URLElicitationRequiredError", + "field": null, + "category": "deliberate-deviation", + "reason": "Named-error wrapper interface; the generic ErrorData envelope plus the error-code constants (and the modeled *ErrorData payloads) cover every wire value (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "errors" + }, + { + "id": "v2025_11_25/UnsubscribeRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "UnsubscribeRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/UnsubscribeRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2025_11_25", + "name": "UnsubscribeRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2025_11_25/UntitledMultiSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "UntitledMultiSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2025_11_25/UntitledSingleSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2025_11_25", + "name": "UntitledSingleSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/Annotations.lastModified#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "Annotations", + "field": "lastModified", + "category": "deliberate-deviation", + "reason": "Annotations.lastModified (since 2025-06-18) is deliberately omitted for now: an existing listing snapshot in tests/interaction/lowlevel/test_resources.py pins the field being dropped, and existing test expectations are frozen on this branch; the field lands together with that snapshot update.", + "track": "content" + }, + { + "id": "v2026_07_28/BooleanSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "BooleanSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/CallToolRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "CallToolRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/CallToolRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "CallToolRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/CallToolResultResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "CallToolResultResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "Typed JSON-RPC response wrappers (result + envelope) have no SDK counterpart: the SDK validates results via the typed Result models and carries the envelope in the generic JSONRPCResponse (untyped result dict).", + "track": "tools" + }, + { + "id": "v2026_07_28/CancelledNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "CancelledNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/CompleteRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "CompleteRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/CompleteRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "CompleteRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/CompleteResultResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "CompleteResultResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "Typed JSON-RPC response wrappers (result + envelope) have no SDK counterpart: the SDK validates results via the typed Result models and carries the envelope in the generic JSONRPCResponse (untyped result dict).", + "track": "completion" + }, + { + "id": "v2026_07_28/Cursor#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "Cursor", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK inlines cursors as plain str fields (cursor/nextCursor) instead of exporting the Cursor alias.", + "track": "pagination-caching" + }, + { + "id": "v2026_07_28/DiscoverRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "DiscoverRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/DiscoverRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "DiscoverRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/DiscoverResultResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "DiscoverResultResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "Typed JSON-RPC response wrappers (result + envelope) have no SDK counterpart: the SDK validates results via the typed Result models and carries the envelope in the generic JSONRPCResponse (untyped result dict).", + "track": "lifecycle-draft" + }, + { + "id": "v2026_07_28/ElicitationCompleteNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ElicitationCompleteNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/EnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "EnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/GetPromptRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "GetPromptRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/GetPromptRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "GetPromptRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/GetPromptResultResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "GetPromptResultResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "Typed JSON-RPC response wrappers (result + envelope) have no SDK counterpart: the SDK validates results via the typed Result models and carries the envelope in the generic JSONRPCResponse (untyped result dict).", + "track": "prompts" + }, + { + "id": "v2026_07_28/Icons#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "Icons", + "field": null, + "category": "deliberate-deviation", + "reason": "Icons is an interface mixin ({icons: Icon[]}); the SDK declares the icons field directly on each type that has one instead of exporting the mixin.", + "track": "metadata-icons" + }, + { + "id": "v2026_07_28/InternalError#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "InternalError", + "field": null, + "category": "deliberate-deviation", + "reason": "Named-error wrapper interface; the generic ErrorData envelope plus the error-code constants (and the modeled *ErrorData payloads) cover every wire value (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "errors" + }, + { + "id": "v2026_07_28/InvalidParamsError#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "InvalidParamsError", + "field": null, + "category": "deliberate-deviation", + "reason": "Named-error wrapper interface; the generic ErrorData envelope plus the error-code constants (and the modeled *ErrorData payloads) cover every wire value (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "errors" + }, + { + "id": "v2026_07_28/InvalidRequestError#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "InvalidRequestError", + "field": null, + "category": "deliberate-deviation", + "reason": "Named-error wrapper interface; the generic ErrorData envelope plus the error-code constants (and the modeled *ErrorData payloads) cover every wire value (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "errors" + }, + { + "id": "v2026_07_28/JSONArray#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "JSONArray", + "field": null, + "category": "deliberate-deviation", + "reason": "2026-07-28 JSON aliases; the SDK uses builtins (Any / dict[str, Any] / list[Any]) instead of named aliases (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "primitives" + }, + { + "id": "v2026_07_28/JSONObject#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "JSONObject", + "field": null, + "category": "deliberate-deviation", + "reason": "2026-07-28 JSON aliases; the SDK uses builtins (Any / dict[str, Any] / list[Any]) instead of named aliases (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "primitives" + }, + { + "id": "v2026_07_28/JSONRPCErrorResponse.id#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2026_07_28", + "name": "JSONRPCErrorResponse", + "field": "id", + "category": "suspected-sdk-bug", + "reason": "SDK requires the id key on error responses; the 2025-11-25+ schemas list only error/jsonrpc as required. JSON-RPC 2.0 requires id on every response (null when no request id could be determined) and every earlier schema version agrees, so the optional id looks like a spec-side regression; the SDK keeps the required-but-nullable id until the spec settles it.", + "track": "envelope" + }, + { + "id": "v2026_07_28/JSONValue#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "JSONValue", + "field": null, + "category": "deliberate-deviation", + "reason": "2026-07-28 JSON aliases; the SDK uses builtins (Any / dict[str, Any] / list[Any]) instead of named aliases (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "primitives" + }, + { + "id": "v2026_07_28/LegacyTitledEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "LegacyTitledEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/ListPromptsRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ListPromptsRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ListPromptsRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ListPromptsRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ListPromptsResultResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "ListPromptsResultResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "Typed JSON-RPC response wrappers (result + envelope) have no SDK counterpart: the SDK validates results via the typed Result models and carries the envelope in the generic JSONRPCResponse (untyped result dict).", + "track": "prompts" + }, + { + "id": "v2026_07_28/ListResourceTemplatesRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ListResourceTemplatesRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ListResourceTemplatesRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ListResourceTemplatesRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ListResourceTemplatesResultResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "ListResourceTemplatesResultResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "Typed JSON-RPC response wrappers (result + envelope) have no SDK counterpart: the SDK validates results via the typed Result models and carries the envelope in the generic JSONRPCResponse (untyped result dict).", + "track": "resources" + }, + { + "id": "v2026_07_28/ListResourcesRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ListResourcesRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ListResourcesRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ListResourcesRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ListResourcesResultResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "ListResourcesResultResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "Typed JSON-RPC response wrappers (result + envelope) have no SDK counterpart: the SDK validates results via the typed Result models and carries the envelope in the generic JSONRPCResponse (untyped result dict).", + "track": "resources" + }, + { + "id": "v2026_07_28/ListToolsRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ListToolsRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ListToolsRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ListToolsRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ListToolsResultResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "ListToolsResultResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "Typed JSON-RPC response wrappers (result + envelope) have no SDK counterpart: the SDK validates results via the typed Result models and carries the envelope in the generic JSONRPCResponse (untyped result dict).", + "track": "tools" + }, + { + "id": "v2026_07_28/LoggingMessageNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "LoggingMessageNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/MetaObject#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "MetaObject", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK models _meta payloads with a private dict[str, Any] alias (Meta in mcp.types._types), not a public MetaObject type.", + "track": "metadata-icons" + }, + { + "id": "v2026_07_28/MethodNotFoundError#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "MethodNotFoundError", + "field": null, + "category": "deliberate-deviation", + "reason": "Named-error wrapper interface; the generic ErrorData envelope plus the error-code constants (and the modeled *ErrorData payloads) cover every wire value (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "errors" + }, + { + "id": "v2026_07_28/MissingRequiredClientCapabilityError#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "MissingRequiredClientCapabilityError", + "field": null, + "category": "deliberate-deviation", + "reason": "Named-error wrapper interface; the generic ErrorData envelope plus the error-code constants (and the modeled *ErrorData payloads) cover every wire value (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "errors" + }, + { + "id": "v2026_07_28/MultiSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "MultiSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/Notification.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2026_07_28", + "name": "Notification", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2026_07_28/NumberSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "NumberSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/PaginatedRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "PaginatedRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/PaginatedRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "PaginatedRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ParseError#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "ParseError", + "field": null, + "category": "deliberate-deviation", + "reason": "Named-error wrapper interface; the generic ErrorData envelope plus the error-code constants (and the modeled *ErrorData payloads) cover every wire value (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "errors" + }, + { + "id": "v2026_07_28/PrimitiveSchemaDefinition#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "PrimitiveSchemaDefinition", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/ProgressNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ProgressNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/PromptListChangedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "PromptListChangedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ReadResourceRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ReadResourceRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ReadResourceRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ReadResourceRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ReadResourceResultResponse#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "ReadResourceResultResponse", + "field": null, + "category": "deliberate-deviation", + "reason": "Typed JSON-RPC response wrappers (result + envelope) have no SDK counterpart: the SDK validates results via the typed Result models and carries the envelope in the generic JSONRPCResponse (untyped result dict).", + "track": "resources" + }, + { + "id": "v2026_07_28/Request.params#SDK-REQUIRED-NOT-IN-SPEC", + "check": "SDK-REQUIRED-NOT-IN-SPEC", + "oracle": "v2026_07_28", + "name": "Request", + "field": "params", + "category": "deliberate-deviation", + "reason": "Generic base class: Request/Notification take params as a TypeVar field with no default, so pydantic reports it required. Every concrete request/notification type overrides params with the spec's per-method requiredness.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ResourceListChangedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ResourceListChangedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/ResourceRequestParams#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "ResourceRequestParams", + "field": null, + "category": "deliberate-deviation", + "reason": "Base interface for resource-request params; the SDK flattens the uri field into each concrete params class instead of exporting the base.", + "track": "resources" + }, + { + "id": "v2026_07_28/ResourceUpdatedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ResourceUpdatedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/Root.uri#TYPE-NARROWER", + "check": "TYPE-NARROWER", + "oracle": "v2026_07_28", + "name": "Root", + "field": "uri", + "category": "deliberate-deviation", + "reason": "SDK uses FileUrl where the schema says format:uri; the spec description for Root.uri states it MUST start with file:// for now, so the SDK encodes the prose constraint in the type.", + "track": "roots" + }, + { + "id": "v2026_07_28/SingleSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "SingleSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/StringSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "StringSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/SubscriptionsAcknowledgedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "SubscriptionsAcknowledgedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/SubscriptionsListenRequest.id#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "SubscriptionsListenRequest", + "field": "id", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/SubscriptionsListenRequest.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "SubscriptionsListenRequest", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/TitledMultiSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "TitledMultiSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/TitledSingleSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "TitledSingleSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/ToolListChangedNotification.jsonrpc#SPEC-FIELD-MISSING", + "check": "SPEC-FIELD-MISSING", + "oracle": "v2026_07_28", + "name": "ToolListChangedNotification", + "field": "jsonrpc", + "category": "deliberate-deviation", + "reason": "The SDK strips the JSON-RPC envelope from typed messages: jsonrpc/id live on JSONRPCRequest/JSONRPCNotification/JSONRPCResponse/JSONRPCError in mcp.types.jsonrpc and are attached by the session layer. The 2025-11-25 schema restructure merged the envelope into every typed request/notification def.", + "track": "envelope" + }, + { + "id": "v2026_07_28/UnsupportedProtocolVersionError#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "UnsupportedProtocolVersionError", + "field": null, + "category": "deliberate-deviation", + "reason": "Named-error wrapper interface; the generic ErrorData envelope plus the error-code constants (and the modeled *ErrorData payloads) cover every wire value (see _spec_names.SCHEMA_NOT_MODELED).", + "track": "errors" + }, + { + "id": "v2026_07_28/UntitledMultiSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "UntitledMultiSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + }, + { + "id": "v2026_07_28/UntitledSingleSelectEnumSchema#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "UntitledSingleSelectEnumSchema", + "field": null, + "category": "deliberate-deviation", + "reason": "The SDK keeps elicitation requestedSchema as an untyped dict (ElicitRequestedSchema = dict[str, Any]) instead of modeling the restricted JSON Schema subset; typing it is a pending curation decision for the superset design.", + "track": "elicitation" + } + ] +} diff --git a/tests/spec_oracles/ext_tasks.py b/tests/spec_oracles/ext_tasks.py new file mode 100644 index 0000000000..3e856fc881 --- /dev/null +++ b/tests/spec_oracles/ext_tasks.py @@ -0,0 +1,695 @@ +# GENERATED FILE — DO NOT EDIT. +# Source: https://github.com/modelcontextprotocol/experimental-ext-tasks/blob/dd47977f4e4069aa4147d816f52ebb9a27c11315/schema/draft/schema.json +# Protocol version: n/a Generator: datamodel-code-generator 0.57.0 +# Regenerate: uv run --frozen python scripts/update_spec_types.py tasks [--sha ] +# pyright: reportIncompatibleVariableOverride=false +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import ConfigDict, Field + +from tests.spec_oracles._base import OracleModel + +McpTasksExtension: TypeAlias = Any + + +Id: TypeAlias = int + + +class Params(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + + +class CancelTaskRequest(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + id: str | Id + method: Literal["tasks/cancel"] + params: Params + + +ProgressToken: TypeAlias = int + + +class IoModelcontextprotocolRelatedTask(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + + +class Meta(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + progress_token: Annotated[str | ProgressToken | None, Field(alias="progressToken")] = None + io_modelcontextprotocol_related_task: Annotated[ + IoModelcontextprotocolRelatedTask | None, + Field(alias="io.modelcontextprotocol/related-task"), + ] = None + + +class CancelTaskResult(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class CancelledTask(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["cancelled"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class CompletedTask(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["completed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + result: dict[str, Any] + + +class Meta1(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + progress_token: Annotated[str | ProgressToken | None, Field(alias="progressToken")] = None + io_modelcontextprotocol_related_task: Annotated[ + IoModelcontextprotocolRelatedTask | None, + Field(alias="io.modelcontextprotocol/related-task"), + ] = None + + +class CreateTaskResult(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta1 | None, Field(alias="_meta")] = None + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["working", "input_required", "completed", "failed", "cancelled"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class DetailedTask1(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["working"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class DetailedTask2(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["input_required"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + input_requests: Annotated[dict[str, Any], Field(alias="inputRequests")] + + +class DetailedTask3(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["completed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + result: dict[str, Any] + + +class DetailedTask4(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["failed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + error: dict[str, Any] + + +class DetailedTask5(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["cancelled"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +DetailedTask: TypeAlias = DetailedTask1 | DetailedTask2 | DetailedTask3 | DetailedTask4 | DetailedTask5 + + +class FailedTask(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["failed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + error: dict[str, Any] + + +class GetTaskRequest(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + id: str | Id + method: Literal["tasks/get"] + params: Params + + +class Meta2(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + progress_token: Annotated[str | ProgressToken | None, Field(alias="progressToken")] = None + io_modelcontextprotocol_related_task: Annotated[ + IoModelcontextprotocolRelatedTask | None, + Field(alias="io.modelcontextprotocol/related-task"), + ] = None + + +class GetTaskResult1(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["working"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class GetTaskResult2(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["input_required"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + input_requests: Annotated[dict[str, Any], Field(alias="inputRequests")] + + +class GetTaskResult3(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["completed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + result: dict[str, Any] + + +class GetTaskResult4(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["failed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + error: dict[str, Any] + + +class GetTaskResult5(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["cancelled"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class GetTaskResult6(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta2 | None, Field(alias="_meta")] = None + + +class GetTaskResult7(GetTaskResult1, GetTaskResult6): + model_config = ConfigDict( + extra="allow", + ) + + +class GetTaskResult8(GetTaskResult2, GetTaskResult6): + model_config = ConfigDict( + extra="allow", + ) + + +class GetTaskResult9(GetTaskResult3, GetTaskResult6): + model_config = ConfigDict( + extra="allow", + ) + + +class GetTaskResult10(GetTaskResult4, GetTaskResult6): + model_config = ConfigDict( + extra="allow", + ) + + +class GetTaskResult11(GetTaskResult5, GetTaskResult6): + model_config = ConfigDict( + extra="allow", + ) + + +GetTaskResult: TypeAlias = GetTaskResult7 | GetTaskResult8 | GetTaskResult9 | GetTaskResult10 | GetTaskResult11 + + +InputRequest: TypeAlias = Any + + +InputRequests: TypeAlias = dict[str, Any] + + +class InputRequiredTask(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["input_required"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + input_requests: Annotated[dict[str, Any], Field(alias="inputRequests")] + + +InputResponse: TypeAlias = Any + + +InputResponses: TypeAlias = dict[str, Any] + + +class Task(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["working", "input_required", "completed", "failed", "cancelled"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class TaskStatusNotificationParams1(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["working"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class TaskStatusNotificationParams2(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["input_required"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + input_requests: Annotated[dict[str, Any], Field(alias="inputRequests")] + + +class TaskStatusNotificationParams3(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["completed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + result: dict[str, Any] + + +class TaskStatusNotificationParams4(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["failed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + error: dict[str, Any] + + +class TaskStatusNotificationParams5(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["cancelled"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class TaskStatusNotificationParams6(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + + +class TaskStatusNotificationParams7(TaskStatusNotificationParams1, TaskStatusNotificationParams6): + model_config = ConfigDict( + extra="allow", + ) + + +class TaskStatusNotificationParams8(TaskStatusNotificationParams2, TaskStatusNotificationParams6): + model_config = ConfigDict( + extra="allow", + ) + + +class TaskStatusNotificationParams9(TaskStatusNotificationParams3, TaskStatusNotificationParams6): + model_config = ConfigDict( + extra="allow", + ) + + +class TaskStatusNotificationParams10(TaskStatusNotificationParams4, TaskStatusNotificationParams6): + model_config = ConfigDict( + extra="allow", + ) + + +class TaskStatusNotificationParams11(TaskStatusNotificationParams5, TaskStatusNotificationParams6): + model_config = ConfigDict( + extra="allow", + ) + + +TaskStatusNotificationParams: TypeAlias = ( + TaskStatusNotificationParams7 + | TaskStatusNotificationParams8 + | TaskStatusNotificationParams9 + | TaskStatusNotificationParams10 + | TaskStatusNotificationParams11 +) + + +class Params21(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["working"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class Params22(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["input_required"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + input_requests: Annotated[dict[str, Any], Field(alias="inputRequests")] + + +class Params23(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["completed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + result: dict[str, Any] + + +class Params24(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["failed"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + error: dict[str, Any] + + +class Params25(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["cancelled"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +class Params26(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + + +class Params27(Params21, Params26): + model_config = ConfigDict( + extra="allow", + ) + + +class Params28(Params22, Params26): + model_config = ConfigDict( + extra="allow", + ) + + +class Params29(Params23, Params26): + model_config = ConfigDict( + extra="allow", + ) + + +class Params210(Params24, Params26): + model_config = ConfigDict( + extra="allow", + ) + + +class Params211(Params25, Params26): + model_config = ConfigDict( + extra="allow", + ) + + +Params2: TypeAlias = Params27 | Params28 | Params29 | Params210 | Params211 + + +class TaskStatusNotification(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/tasks"] + params: Params2 + + +TaskStatus: TypeAlias = Literal["working", "input_required", "completed", "failed", "cancelled"] + + +class TaskSubscriptionAcknowledgedNotifications(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_ids: Annotated[list[str] | None, Field(alias="taskIds")] = None + + +class TaskSubscriptionNotifications(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_ids: Annotated[list[str] | None, Field(alias="taskIds")] = None + + +TasksExtensionCapability: TypeAlias = dict[str, Any] + + +class Params3(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + input_responses: Annotated[dict[str, Any], Field(alias="inputResponses")] + + +class UpdateTaskRequest(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + id: str | Id + method: Literal["tasks/update"] + params: Params3 + + +class Meta3(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + progress_token: Annotated[str | ProgressToken | None, Field(alias="progressToken")] = None + io_modelcontextprotocol_related_task: Annotated[ + IoModelcontextprotocolRelatedTask | None, + Field(alias="io.modelcontextprotocol/related-task"), + ] = None + + +class UpdateTaskResult(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta3 | None, Field(alias="_meta")] = None + + +class WorkingTask(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + status: Literal["working"] + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + created_at: Annotated[str, Field(alias="createdAt")] + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + ttl_ms: Annotated[float | None, Field(alias="ttlMs")] + poll_interval_ms: Annotated[float | None, Field(alias="pollIntervalMs")] = None + + +SPEC_DEFS: tuple[str, ...] = ( + "CancelTaskRequest", + "CancelTaskResult", + "CancelledTask", + "CompletedTask", + "CreateTaskResult", + "DetailedTask", + "FailedTask", + "GetTaskRequest", + "GetTaskResult", + "InputRequest", + "InputRequests", + "InputRequiredTask", + "InputResponse", + "InputResponses", + "Task", + "TaskStatus", + "TaskStatusNotification", + "TaskStatusNotificationParams", + "TaskSubscriptionAcknowledgedNotifications", + "TaskSubscriptionNotifications", + "TasksExtensionCapability", + "UpdateTaskRequest", + "UpdateTaskResult", + "WorkingTask", +) diff --git a/tests/spec_oracles/test_burndown.py b/tests/spec_oracles/test_burndown.py new file mode 100644 index 0000000000..fecc827dea --- /dev/null +++ b/tests/spec_oracles/test_burndown.py @@ -0,0 +1,136 @@ +"""Burn-down gate: generated spec oracles vs the SDK's curated types. + +Fails on (a) any hard finding not in burndown_allowlist.json, and (b) any +allowlist entry that no longer fires (stale entry - remove it). Together the +two directions make the allowlist a burn-down list: implementing a type or +field forces the corresponding entries out of the file. +""" + +from __future__ import annotations + +import pytest + +from tests.spec_oracles import _harness as h + + +@pytest.fixture(scope="module") +def entries() -> list[h.AllowlistEntry]: + return h.load_allowlist() + + +@pytest.fixture(scope="module") +def evaluation(entries: list[h.AllowlistEntry]) -> h.Evaluation: + findings = h.all_findings(h.gap_paths(entries)) + return h.evaluate(findings, entries) + + +def _format(findings: tuple[h.Finding, ...]) -> str: + lines = [f" {f.id}\n {f.detail}" for f in findings] + return "\n".join(lines) + + +@pytest.mark.parametrize("oracle", [*h.ORACLE_MODULES, "sdk"]) +def test_no_unallowlisted_hard_findings(oracle: str, evaluation: h.Evaluation) -> None: + new = tuple(f for f in evaluation.new_hard if f.oracle == oracle) + assert not new, ( + f"{len(new)} hard finding(s) for {oracle} not in burndown_allowlist.json - " + f"either fix the SDK divergence or add a categorized entry:\n{_format(new)}" + ) + + +def test_no_stale_allowlist_entries(evaluation: h.Evaluation) -> None: + stale = evaluation.stale_entries + assert not stale, ( + f"{len(stale)} allowlist entr(ies) no longer fire - the divergence was fixed, " + "so remove them from burndown_allowlist.json (the burn-down ratchet):\n" + + "\n".join(f" {e.id} ({e.category})" for e in stale) + ) + + +def test_allowlist_entries_well_formed(entries: list[h.AllowlistEntry]) -> None: + # load_allowlist already validates ids, categories, checks, and uniqueness; + # this pins the one invariant the loader does not enforce. + for entry in entries: + assert entry.oracle in (*h.ORACLE_MODULES, "sdk") + + +# --- harness unit tests (synthetic data; the ratchet must work both ways) --- + + +def _finding(check: str = "SPEC-TYPE-MISSING", name: str = "Foo") -> h.Finding: + return h.Finding(check=check, oracle="v2026_07_28", name=name, field=None, detail="synthetic") + + +def _entry( + check: str = "SPEC-TYPE-MISSING", name: str = "Foo", category: str = "not-yet-implemented" +) -> h.AllowlistEntry: + return h.AllowlistEntry( + id=f"v2026_07_28/{name}#{check}", + check=check, + oracle="v2026_07_28", + name=name, + field=None, + category=category, + reason="synthetic", + track=None, + ) + + +def test_evaluate_flags_unallowlisted_hard_finding() -> None: + result = h.evaluate([_finding()], []) + assert result.new_hard == (_finding(),) + assert result.stale_entries == () + + +def test_evaluate_matches_allowlisted_finding_by_id() -> None: + result = h.evaluate([_finding()], [_entry()]) + assert result.new_hard == () + assert result.stale_entries == () + assert result.allowlisted_hard == (_finding(),) + + +def test_evaluate_flags_stale_entry() -> None: + result = h.evaluate([], [_entry()]) + assert result.stale_entries == (_entry(),) + + +def test_evaluate_soft_findings_never_fail() -> None: + soft = h.Finding(check="TYPE-WIDER", oracle="v2026_07_28", name="Foo", field="bar", detail="synthetic") + result = h.evaluate([soft], []) + assert result.new_hard == () + assert result.soft == (soft,) + + +def test_schema_gap_entry_stays_live_while_gap_exists() -> None: + live = h.AllowlistEntry( + id="ext_tasks/InputRequest#VACUOUS-SCHEMA", + check="VACUOUS-SCHEMA", + oracle="ext_tasks", + name="InputRequest", + field=None, + category="schema-gap", + reason="synthetic", + track=None, + ) + assert h.schema_gap_applies(live) + result = h.evaluate([], [live]) + assert result.stale_entries == () + + +def test_schema_gap_entry_goes_stale_when_gap_is_fixed() -> None: + # Task.taskId is a real, fully-typed site: a gap entry pointing at it must + # be reported stale (this is what fires when a future ext-tasks pin + # restores the lost $refs and regeneration removes the Any). + fixed = h.AllowlistEntry( + id="ext_tasks/Task.taskId#VACUOUS-SCHEMA", + check="VACUOUS-SCHEMA", + oracle="ext_tasks", + name="Task", + field="taskId", + category="schema-gap", + reason="synthetic", + track=None, + ) + assert not h.schema_gap_applies(fixed) + result = h.evaluate([], [fixed]) + assert result.stale_entries == (fixed,) diff --git a/tests/spec_oracles/test_harness_internals.py b/tests/spec_oracles/test_harness_internals.py new file mode 100644 index 0000000000..105f9135d9 --- /dev/null +++ b/tests/spec_oracles/test_harness_internals.py @@ -0,0 +1,277 @@ +"""Edge-case pins for the burn-down harness's own helpers. + +The comparison harness is test infrastructure: its loader validation, type +algebra, and gap machinery decide what the burn-down gate accepts. These tests +pin the defensive branches directly with synthetic inputs so a regression in +the harness itself cannot silently weaken the gate. +""" + +from __future__ import annotations + +import json +import types +from pathlib import Path +from typing import Any + +import pytest +from pydantic import Base64Str, BaseModel +from typing_extensions import TypeAliasType + +from tests.spec_oracles import _harness as h +from tests.spec_oracles.test_burndown import _format + +# --- load_allowlist validation ----------------------------------------------- + + +def _write_allowlist(path: Path, entries: list[dict[str, Any]]) -> Path: + target = path / "allowlist.json" + target.write_text(json.dumps({"entries": entries})) + return target + + +def _raw_entry(**overrides: Any) -> dict[str, Any]: + entry: dict[str, Any] = { + "id": "v2026_07_28/Foo#SPEC-TYPE-MISSING", + "check": "SPEC-TYPE-MISSING", + "oracle": "v2026_07_28", + "name": "Foo", + "category": "not-yet-implemented", + "reason": "synthetic", + } + entry.update(overrides) + return entry + + +def test_loader_rejects_id_not_matching_its_parts(tmp_path: Path) -> None: + path = _write_allowlist(tmp_path, [_raw_entry(id="v2026_07_28/Bar#SPEC-TYPE-MISSING")]) + with pytest.raises(ValueError, match="does not match its parts"): + h.load_allowlist(path) + + +def test_loader_rejects_unknown_category(tmp_path: Path) -> None: + path = _write_allowlist(tmp_path, [_raw_entry(category="wontfix")]) + with pytest.raises(ValueError, match="unknown category"): + h.load_allowlist(path) + + +def test_loader_rejects_soft_checks(tmp_path: Path) -> None: + # Soft findings never fail the gate, so allowlisting one is a mistake. + path = _write_allowlist(tmp_path, [_raw_entry(id="v2026_07_28/Foo#TYPE-WIDER", check="TYPE-WIDER")]) + with pytest.raises(ValueError, match="only hard findings"): + h.load_allowlist(path) + + +def test_loader_rejects_gap_check_without_gap_category(tmp_path: Path) -> None: + path = _write_allowlist(tmp_path, [_raw_entry(id="v2026_07_28/Foo#VACUOUS-SCHEMA", check="VACUOUS-SCHEMA")]) + with pytest.raises(ValueError, match="must be category schema-gap"): + h.load_allowlist(path) + + +def test_loader_rejects_gap_category_on_hard_check(tmp_path: Path) -> None: + path = _write_allowlist(tmp_path, [_raw_entry(category="schema-gap")]) + with pytest.raises(ValueError, match="must use a gap pseudo-check"): + h.load_allowlist(path) + + +def test_loader_rejects_duplicate_ids(tmp_path: Path) -> None: + path = _write_allowlist(tmp_path, [_raw_entry(), _raw_entry()]) + with pytest.raises(ValueError, match="duplicate allowlist ids"): + h.load_allowlist(path) + + +# --- sig: annotation canonicalization ---------------------------------------- + + +def test_sig_canonicalizes_base64_strings() -> None: + assert h.sig(Base64Str, sdk=False) == ("base64",) + + +def test_sig_marks_recursive_aliases_instead_of_recursing() -> None: + # A self-referential alias must bottom out in a named marker rather than + # recursing forever; the cycle check keys on the alias name. + placeholder = TypeAliasType("Rec", int) + rec = TypeAliasType("Rec", list[placeholder]) + assert h.sig(rec, sdk=False) == ("list", ("recursive", "Rec")) + + +def test_sig_collapses_union_members_with_identical_signatures() -> None: + # An alias of list[int] and list[int] itself are distinct annotation + # objects with the same canonical signature; the union folds to one member. + alias = TypeAliasType("IntList", list[int]) + assert h.sig(alias | list[int], sdk=False) == ("list", ("prim", "int")) + + +def test_sig_treats_bare_dict_as_open_mapping() -> None: + # The legacy bare-Dict spelling carries a dict origin with no type + # arguments; the canonical form is an open mapping. + bare_dict = types.GenericAlias(dict, ()) + assert h.sig(bare_dict, sdk=False) == ("dict", ("any",), ("any",)) + + +def test_sig_marks_unknown_classes_opaque() -> None: + class NotAModel: + pass + + kind, detail = h.sig(NotAModel, sdk=False) + assert kind == "opaque" + assert detail.endswith("NotAModel") + + +# --- the optionality-stripping helper ----------------------------------------- + + +def test_strip_optional_null_keeps_plain_null_for_null_only_unions() -> None: + assert h._strip_optional_null(("union", frozenset({("null",)}))) == ("null",) + + +# --- compat: the type algebra's primitive relations ---------------------------- + + +@pytest.mark.parametrize( + ("spec", "sdk", "expected"), + [ + # An Any on the spec side means the oracle accepts everything; any + # concrete SDK annotation is narrower. + (("any",), ("prim", "int"), "sdk_narrower"), + # Numeric widening is directional. + (("prim", "int"), ("prim", "float"), "sdk_wider"), + (("prim", "float"), ("prim", "int"), "sdk_narrower"), + (("prim", "str"), ("prim", "bool"), "incomparable"), + # Literal sets compare by inclusion. + (("lit", frozenset({"a"})), ("lit", frozenset({"a", "b"})), "sdk_wider"), + (("lit", frozenset({"a", "b"})), ("lit", frozenset({"a"})), "sdk_narrower"), + # A union member rejected without any incomparable verdict keeps the + # overall relation at narrower while later members are still walked. + ( + ("union", frozenset({("prim", "float"), ("prim", "int")})), + ("prim", "int"), + "sdk_narrower", + ), + # A Literal against its base primitive widens; against a different + # primitive the algebra cannot relate them. + (("lit", frozenset({"a", "b"})), ("prim", "str"), "sdk_wider"), + (("lit", frozenset({"a"})), ("prim", "int"), "incomparable"), + (("prim", "str"), ("lit", frozenset({"a"})), "sdk_narrower"), + (("prim", "int"), ("lit", frozenset({"a"})), "incomparable"), + # Base64-constrained strings sit inside plain strings. + (("base64",), ("prim", "str"), "sdk_wider"), + (("base64",), ("prim", "int"), "incomparable"), + (("prim", "str"), ("base64",), "sdk_narrower"), + (("prim", "int"), ("base64",), "incomparable"), + # File URLs sit inside general URLs, and URLs inside plain strings. + (("url", "any"), ("url", "file"), "sdk_narrower"), + (("url", "file"), ("url", "any"), "sdk_wider"), + (("url", "any"), ("prim", "str"), "sdk_wider"), + (("prim", "str"), ("url", "any"), "sdk_narrower"), + # Containers compare element-wise; dicts take the worst of key/value. + (("list", ("prim", "int")), ("list", ("prim", "float")), "sdk_wider"), + ( + ("dict", ("prim", "str"), ("prim", "float")), + ("dict", ("prim", "str"), ("prim", "int")), + "sdk_narrower", + ), + # Differently named models are out of the algebra's reach. + (("model", "Foo"), ("model", "Bar"), "incomparable"), + ], +) +def test_compat_primitive_relations(spec: h.Sig, sdk: h.Sig, expected: str) -> None: + assert h.compat(spec, sdk) == expected + + +# --- field comparison: schema-gap paths are skipped ---------------------------- + + +class _SpecModel(BaseModel): + value: int + + +class _SdkModel(BaseModel): + value: str + + +def test_field_comparison_skips_gap_paths() -> None: + gap: h.GapPaths = frozenset({("v2026_07_28", "Synthetic", "value")}) + without_gap = h._compare_models("v2026_07_28", "Synthetic", _SpecModel, _SdkModel, frozenset()) + with_gap = h._compare_models("v2026_07_28", "Synthetic", _SpecModel, _SdkModel, gap) + assert any(f.check == "TYPE-INCOMPARABLE" for f in without_gap) + assert with_gap == [] + + +# --- aggregated checks: alias-only pairings carry no field information --------- + + +def test_phantom_field_check_skips_names_paired_only_to_aliases(monkeypatch: pytest.MonkeyPatch) -> None: + # Cursor is a plain alias def: a model paired only to alias defs has no + # oracle field set to compare against, so no field findings are emitted. + monkeypatch.setattr(h, "_sdk_public_names", lambda: ["CallToolResult"]) + monkeypatch.setattr(h, "_sdk_to_oracle_defs", lambda: {"CallToolResult": [("v2025_03_26", "Cursor")]}) + assert h.aggregated_findings() == [] + + +# --- schema_gap_applies: liveness probes for gap exemptions -------------------- + + +def _gap_entry(check: str, name: str, field: str | None = None) -> h.AllowlistEntry: + field_part = f".{field}" if field is not None else "" + return h.AllowlistEntry( + id=f"ext_tasks/{name}{field_part}#{check}", + check=check, + oracle="ext_tasks", + name=name, + field=field, + category="schema-gap", + reason="synthetic", + track=None, + ) + + +def test_gap_entry_for_a_vanished_def_is_stale() -> None: + assert not h.schema_gap_applies(_gap_entry("VACUOUS-SCHEMA", "NoSuchDef")) + + +def test_required_unverifiable_needs_a_model_def() -> None: + # InputRequest resolves to a plain alias, not a model, so the lost + # `required`-array exemption cannot apply to it. + assert not h.schema_gap_applies(_gap_entry("REQUIRED-UNVERIFIABLE", "InputRequest")) + + +def test_schema_gap_probe_rejects_non_gap_checks() -> None: + with pytest.raises(ValueError, match="not a schema-gap pseudo-check"): + h.schema_gap_applies(_gap_entry("SPEC-TYPE-MISSING", "Task")) + + +# --- closure and Any-detection helpers ----------------------------------------- + + +class _Leaf(BaseModel): + name: str + + +def test_closure_walks_through_type_aliases() -> None: + alias = TypeAliasType("LeafAlias", _Leaf) + seen: set[type[BaseModel]] = set() + h._closure_models(alias, seen) + assert seen == {_Leaf} + + +@pytest.mark.parametrize( + ("signature", "expected"), + [ + (("union", frozenset({("any",)})), True), + (("union", frozenset({("prim", "str")})), False), + (("list", ("any",)), True), + (("list", ("prim", "int")), False), + ], +) +def test_contains_any_descends_into_unions_and_lists(signature: h.Sig, expected: bool) -> None: + assert h._contains_any(signature) is expected + + +# --- the gate's failure formatter ---------------------------------------------- + + +def test_failure_formatter_lists_finding_ids_with_details() -> None: + finding = h.Finding(check="SPEC-TYPE-MISSING", oracle="v2026_07_28", name="Foo", field=None, detail="synthetic") + text = _format((finding,)) + assert "v2026_07_28/Foo#SPEC-TYPE-MISSING" in text + assert "synthetic" in text diff --git a/tests/spec_oracles/test_oracles_smoke.py b/tests/spec_oracles/test_oracles_smoke.py new file mode 100644 index 0000000000..d7ab6f3ba9 --- /dev/null +++ b/tests/spec_oracles/test_oracles_smoke.py @@ -0,0 +1,93 @@ +"""Wire-fidelity smoke tests for the generated spec-oracle modules. + +The burn-down comparison in `test_burndown.py` inspects model shapes +statically; these hand-written wire fixtures check that the oracle models +also validate and re-dump real frames: `_meta` reserved-key round-trips, +content-union discrimination, the per-version `resultType` split, the +2026-07-28 replacement of the initialize handshake with `server/discover`, +ext-tasks basics, and extra-field retention. +""" + +from __future__ import annotations + +import pytest +from pydantic import TypeAdapter, ValidationError + +from tests.spec_oracles import ext_tasks, v2024_11_05, v2026_07_28 + +CALL_TOOL_REQUEST_2026_07_28 = { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "_meta": { + "io.modelcontextprotocol/clientCapabilities": {}, + "io.modelcontextprotocol/clientInfo": {"name": "smoke-client", "version": "0.0.1"}, + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + }, + "name": "echo", + "arguments": {"text": "hi"}, + }, +} + + +def test_call_tool_request_meta_round_trips_in_2026_07_28() -> None: + request = v2026_07_28.CallToolRequest.model_validate(CALL_TOOL_REQUEST_2026_07_28) + meta = request.params.meta + assert meta.io_modelcontextprotocol_protocol_version == "2026-07-28" + assert meta.io_modelcontextprotocol_client_info.name == "smoke-client" + dumped = request.model_dump(by_alias=True, exclude_none=True, mode="json") + assert dumped == CALL_TOOL_REQUEST_2026_07_28 + + +def test_content_union_discriminates_in_2026_07_28() -> None: + adapter: TypeAdapter[v2026_07_28.ContentBlock] = TypeAdapter(v2026_07_28.ContentBlock) + text = adapter.validate_python({"type": "text", "text": "hello"}) + assert isinstance(text, v2026_07_28.TextContent) + link = adapter.validate_python({"type": "resource_link", "name": "r", "uri": "https://example.com/r"}) + assert isinstance(link, v2026_07_28.ResourceLink) + with pytest.raises(ValidationError): + adapter.validate_python({"type": "nope"}) + + +def test_call_tool_result_has_no_result_type_in_2024_11_05() -> None: + result = v2024_11_05.CallToolResult.model_validate({"content": [{"type": "text", "text": "ok"}]}) + assert "result_type" not in v2024_11_05.CallToolResult.model_fields + assert isinstance(result.content[0], v2024_11_05.TextContent) + + +def test_result_requires_result_type_in_2026_07_28() -> None: + result = v2026_07_28.Result.model_validate({"resultType": "callTool"}) + assert result.result_type == "callTool" + with pytest.raises(ValidationError): + v2026_07_28.Result.model_validate({}) + + +def test_initialize_dropped_and_discover_added_in_2026_07_28() -> None: + assert not hasattr(v2026_07_28, "InitializeRequest") + assert not hasattr(v2026_07_28, "InitializeResult") + assert hasattr(v2026_07_28, "DiscoverRequest") + assert hasattr(v2026_07_28, "DiscoverResult") + assert hasattr(v2024_11_05, "InitializeRequest") + + +def test_ext_tasks_minimal_task_and_manifest() -> None: + task = ext_tasks.Task.model_validate( + { + "taskId": "t-1", + "status": "working", + "createdAt": "2026-06-05T00:00:00Z", + "lastUpdatedAt": "2026-06-05T00:00:00Z", + "ttlMs": None, + } + ) + assert task.task_id == "t-1" + assert len(ext_tasks.SPEC_DEFS) == 24 + for name in ext_tasks.SPEC_DEFS: + assert getattr(ext_tasks, name, None) is not None + + +def test_extra_fields_survive_round_trip() -> None: + payload = {"resultType": "callTool", "x-vendor-key": {"nested": 1}} + result = v2026_07_28.Result.model_validate(payload) + assert result.model_dump(by_alias=True, exclude_none=True, mode="json") == payload diff --git a/tests/spec_oracles/v2024_11_05.py b/tests/spec_oracles/v2024_11_05.py new file mode 100644 index 0000000000..f83f370a8b --- /dev/null +++ b/tests/spec_oracles/v2024_11_05.py @@ -0,0 +1,1590 @@ +# GENERATED FILE — DO NOT EDIT. +# Source: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/6d441518de8a9d5adbab0b10a76a667a63f90665/schema/2024-11-05/schema.json +# Protocol version: 2024-11-05 Generator: datamodel-code-generator 0.57.0 +# Regenerate: uv run --frozen python scripts/update_spec_types.py 2024-11-05 [--sha ] +# pyright: reportIncompatibleVariableOverride=false +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import AnyUrl, Base64Str, ConfigDict, Field + +from tests.spec_oracles._base import OracleModel + + +class BlobResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + blob: Base64Str + """ + A base64-encoded string representing the binary data of the item. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class Params(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + arguments: dict[str, Any] | None = None + name: str + + +class CallToolRequest(OracleModel): + """Used by the client to invoke a tool provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["tools/call"] + params: Params + + +class Roots(OracleModel): + """Present if the client supports listing roots.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether the client supports notifications for changes to the roots list. + """ + + +class ClientCapabilities(OracleModel): + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ + roots: Roots | None = None + """ + Present if the client supports listing roots. + """ + sampling: dict[str, Any] | None = None + """ + Present if the client supports sampling from an LLM. + """ + + +class Argument(OracleModel): + """The argument's information""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + The name of the argument + """ + value: str + """ + The value of the argument to use for completion matching. + """ + + +class Completion(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + has_more: Annotated[bool | None, Field(alias="hasMore")] = None + """ + Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. + """ + total: int | None = None + """ + The total number of completion options available. This can exceed the number of values actually sent in the response. + """ + values: list[str] + """ + An array of completion values. Must not exceed 100 items. + """ + + +class CompleteResult(OracleModel): + """The server's response to a completion/complete request""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + completion: Completion + + +Cursor: TypeAlias = str + + +class Params4(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ + name: str + """ + The name of the prompt or prompt template. + """ + + +class GetPromptRequest(OracleModel): + """Used by the client to get a prompt provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["prompts/get"] + params: Params4 + + +class Implementation(OracleModel): + """Describes the name and version of an MCP implementation.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + version: str + + +class Params5(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + capabilities: ClientCapabilities + client_info: Annotated[Implementation, Field(alias="clientInfo")] + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. + """ + + +class InitializeRequest(OracleModel): + """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["initialize"] + params: Params5 + + +class Params6(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class InitializedNotification(OracleModel): + """This notification is sent from the client to the server after initialization has finished.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/initialized"] + params: Params6 | None = None + + +class Error(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + code: int + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class JSONRPCNotification(OracleModel): + """A notification which does not expect a response.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: str + params: Params6 | None = None + + +class Params9(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class ListPromptsRequest(OracleModel): + """Sent from the client to request a list of prompts and prompt templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["prompts/list"] + params: Params9 | None = None + + +class ListResourceTemplatesRequest(OracleModel): + """Sent from the client to request a list of resource templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/templates/list"] + params: Params9 | None = None + + +class ListResourcesRequest(OracleModel): + """Sent from the client to request a list of resources the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/list"] + params: Params9 | None = None + + +class ListToolsRequest(OracleModel): + """Sent from the client to request a list of tools the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["tools/list"] + params: Params9 | None = None + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +class Params14(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ + level: LoggingLevel + """ + The severity of this log message. + """ + logger: str | None = None + """ + An optional name of the logger issuing this message. + """ + + +class LoggingMessageNotification(OracleModel): + """Notification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/message"] + params: Params14 + + +class ModelHint(OracleModel): + """Hints to use for model selection. + + Keys not declared here are currently left unspecified by the spec and are up + to the client to interpret. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str | None = None + """ + A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` + """ + + +class ModelPreferences(OracleModel): + """The server's preferences for model selection, requested of the client during sampling. + + Because LLMs can vary along multiple dimensions, choosing the "best" model is + rarely straightforward. Different models excel in different areas—some are + faster but less capable, others are more capable but more expensive, and so + on. This interface allows servers to express their priorities across multiple + dimensions to help clients make an appropriate selection for their use case. + + These preferences are always advisory. The client MAY ignore them. It is also + up to the client to decide how to interpret these preferences and how to + balance them against other considerations. + """ + + model_config = ConfigDict( + extra="allow", + ) + cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + """ + hints: list[ModelHint] | None = None + """ + Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. + """ + intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + """ + speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + """ + + +class Params15(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class Notification(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: Params15 | None = None + + +class Params16(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class PaginatedRequest(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: Params16 | None = None + + +class PaginatedResult(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + + +ProgressToken: TypeAlias = str | int + + +class PromptArgument(OracleModel): + """Describes an argument that a prompt can accept.""" + + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + """ + A human-readable description of the argument. + """ + name: str + """ + The name of the argument. + """ + required: bool | None = None + """ + Whether this argument must be provided. + """ + + +class Params19(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class PromptListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/prompts/list_changed"] + params: Params19 | None = None + + +class PromptReference(OracleModel): + """Identifies a prompt.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + The name of the prompt or prompt template + """ + type: Literal["ref/prompt"] + + +class Params20(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class ReadResourceRequest(OracleModel): + """Sent from the client to the server, to read a specific resource URI.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/read"] + params: Params20 + + +class Meta(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + """ + + +class Params21(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class Request(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: Params21 | None = None + + +RequestId: TypeAlias = str | int + + +class ResourceContents(OracleModel): + """The contents of a specific resource or sub-resource.""" + + model_config = ConfigDict( + extra="allow", + ) + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class Params22(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class ResourceListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/resources/list_changed"] + params: Params22 | None = None + + +class ResourceReference(OracleModel): + """A reference to a resource or resource template definition.""" + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["ref/resource"] + uri: str + """ + The URI or URI template of the resource. + """ + + +class Params23(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. + """ + + +class ResourceUpdatedNotification(OracleModel): + """A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/resources/updated"] + params: Params23 + + +class Result(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + + +Role: TypeAlias = Literal["assistant", "user"] + + +class Root(OracleModel): + """Represents a root directory or file that the server can operate on.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ + uri: AnyUrl + """ + The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ + + +class Params24(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class RootsListChangedNotification(OracleModel): + """A notification from the client to the server, informing it that the list of roots has changed. + This notification should be sent whenever the client adds, removes, or modifies any root. + The server should then request an updated list of roots using the ListRootsRequest. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/roots/list_changed"] + params: Params24 | None = None + + +class Prompts(OracleModel): + """Present if the server offers any prompt templates.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the prompt list. + """ + + +class Resources(OracleModel): + """Present if the server offers any resources to read.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the resource list. + """ + subscribe: bool | None = None + """ + Whether this server supports subscribing to resource updates. + """ + + +class Tools(OracleModel): + """Present if the server offers any tools to call.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the tool list. + """ + + +class ServerCapabilities(OracleModel): + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ + logging: dict[str, Any] | None = None + """ + Present if the server supports sending log messages to the client. + """ + prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ + resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ + tools: Tools | None = None + """ + Present if the server offers any tools to call. + """ + + +class Params25(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + level: LoggingLevel + """ + The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message. + """ + + +class SetLevelRequest(OracleModel): + """A request from the client to the server, to enable or adjust logging.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["logging/setLevel"] + params: Params25 + + +class Params26(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class SubscribeRequest(OracleModel): + """Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/subscribe"] + params: Params26 + + +class Annotations(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + audience: list[Role] | None = None + """ + Describes who the intended customer of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). + """ + priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None + """ + Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + """ + + +class TextContent(OracleModel): + """Text provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + text: str + """ + The text content of the message. + """ + type: Literal["text"] + + +class TextResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class InputSchema(OracleModel): + """A JSON Schema object defining the expected parameters for the tool.""" + + model_config = ConfigDict( + extra="allow", + ) + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class Tool(OracleModel): + """Definition for a tool the client can call.""" + + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + """ + A human-readable description of the tool. + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + """ + name: str + """ + The name of the tool. + """ + + +class Params27(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class ToolListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/tools/list_changed"] + params: Params27 | None = None + + +class Params28(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource to unsubscribe from. + """ + + +class UnsubscribeRequest(OracleModel): + """Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/unsubscribe"] + params: Params28 + + +class AnnotatedModel(OracleModel): + """Base for objects that include optional annotations for the client. The client can use annotations to inform how objects are used or displayed""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + + +class Params1(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ + request_id: Annotated[RequestId, Field(alias="requestId")] + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + """ + + +class CancelledNotification(OracleModel): + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + + A client MUST NOT attempt to cancel its `initialize` request. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/cancelled"] + params: Params1 + + +class Params2(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + argument: Argument + """ + The argument's information + """ + ref: PromptReference | ResourceReference + + +class CompleteRequest(OracleModel): + """A request from the client to the server, to ask for completion options.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["completion/complete"] + params: Params2 + + +class EmbeddedResource(OracleModel): + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +EmptyResult: TypeAlias = Result + + +class ImageContent(OracleModel): + """An image provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + data: Base64Str + """ + The base64-encoded image data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ + type: Literal["image"] + + +class InitializeResult(OracleModel): + """After receiving an initialize request from the client, the server sends this response.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + capabilities: ServerCapabilities + instructions: str | None = None + """ + Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. + """ + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class JSONRPCError(OracleModel): + """A response to a request that indicates an error occurred.""" + + model_config = ConfigDict( + extra="allow", + ) + error: Error + id: RequestId + jsonrpc: Literal["2.0"] + + +class Params8(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class JSONRPCRequest(OracleModel): + """A request that expects a response.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: Params8 | None = None + + +class JSONRPCResponse(OracleModel): + """A successful (non-error) response to a request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class Params12(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class ListRootsRequest(OracleModel): + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["roots/list"] + params: Params12 | None = None + + +class ListRootsResult(OracleModel): + """The client's response to a roots/list request from the server. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + roots: list[Root] + + +class ListToolsResult(OracleModel): + """The server's response to a tools/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tools: list[Tool] + + +class Params17(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class PingRequest(OracleModel): + """A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["ping"] + params: Params17 | None = None + + +class Params18(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """ + The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. + """ + total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ + + +class ProgressNotification(OracleModel): + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/progress"] + params: Params18 + + +class Prompt(OracleModel): + """A prompt or prompt template that the server offers.""" + + model_config = ConfigDict( + extra="allow", + ) + arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ + description: str | None = None + """ + An optional description of what this prompt provides + """ + name: str + """ + The name of the prompt or prompt template. + """ + + +class PromptMessage(OracleModel): + """Describes a message returned as part of a prompt. + + This is similar to `SamplingMessage`, but also supports the embedding of + resources from the MCP server. + """ + + model_config = ConfigDict( + extra="allow", + ) + content: TextContent | ImageContent | EmbeddedResource + role: Role + + +class ReadResourceResult(OracleModel): + """The server's response to a resources/read request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + contents: list[TextResourceContents | BlobResourceContents] + + +class Resource(OracleModel): + """A known resource that the server is capable of reading.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + A human-readable name for this resource. + + This can be used by clients to populate UI elements. + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceTemplate(OracleModel): + """A template description for resources available on the server.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. + """ + name: str + """ + A human-readable name for the type of resource this template refers to. + + This can be used by clients to populate UI elements. + """ + uri_template: Annotated[str, Field(alias="uriTemplate")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +class SamplingMessage(OracleModel): + """Describes a message issued to or received from an LLM API.""" + + model_config = ConfigDict( + extra="allow", + ) + content: TextContent | ImageContent + role: Role + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification +) + + +class CallToolResult(OracleModel): + """The server's response to a tool call. + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + content: list[TextContent | ImageContent | EmbeddedResource] + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + """ + + +ClientNotification: TypeAlias = ( + CancelledNotification | InitializedNotification | ProgressNotification | RootsListChangedNotification +) + + +ClientRequest: TypeAlias = ( + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | SetLevelRequest + | CompleteRequest +) + + +class Params3(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. + """ + max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested. + """ + messages: list[SamplingMessage] + metadata: dict[str, Any] | None = None + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ + temperature: float | None = None + + +class CreateMessageRequest(OracleModel): + """A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["sampling/createMessage"] + params: Params3 + + +class CreateMessageResult(OracleModel): + """The client's response to a sampling/create_message request from the server. The client should inform the user before returning the sampled message, to allow them to inspect the response (human in the loop) and decide whether to allow the server to see it.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + content: TextContent | ImageContent + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + The reason why sampling stopped, if known. + """ + + +class GetPromptResult(OracleModel): + """The server's response to a prompts/get request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + description: str | None = None + """ + An optional description for the prompt. + """ + messages: list[PromptMessage] + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError + + +class ListPromptsResult(OracleModel): + """The server's response to a prompts/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + prompts: list[Prompt] + + +class ListResourceTemplatesResult(OracleModel): + """The server's response to a resources/templates/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + + +class ListResourcesResult(OracleModel): + """The server's response to a resources/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resources: list[Resource] + + +ServerRequest: TypeAlias = PingRequest | CreateMessageRequest | ListRootsRequest + + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) + + +ClientResult: TypeAlias = Result | CreateMessageResult | ListRootsResult + +SPEC_DEFS: tuple[str, ...] = ( + "Annotated", + "BlobResourceContents", + "CallToolRequest", + "CallToolResult", + "CancelledNotification", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteResult", + "CreateMessageRequest", + "CreateMessageResult", + "Cursor", + "EmbeddedResource", + "EmptyResult", + "GetPromptRequest", + "GetPromptResult", + "ImageContent", + "Implementation", + "InitializeRequest", + "InitializeResult", + "InitializedNotification", + "JSONRPCError", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "ModelHint", + "ModelPreferences", + "Notification", + "PaginatedRequest", + "PaginatedResult", + "PingRequest", + "ProgressNotification", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceResult", + "Request", + "RequestId", + "Resource", + "ResourceContents", + "ResourceListChangedNotification", + "ResourceReference", + "ResourceTemplate", + "ResourceUpdatedNotification", + "Result", + "Role", + "Root", + "RootsListChangedNotification", + "SamplingMessage", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "SubscribeRequest", + "TextContent", + "TextResourceContents", + "Tool", + "ToolListChangedNotification", + "UnsubscribeRequest", +) diff --git a/tests/spec_oracles/v2025_03_26.py b/tests/spec_oracles/v2025_03_26.py new file mode 100644 index 0000000000..48de83f35c --- /dev/null +++ b/tests/spec_oracles/v2025_03_26.py @@ -0,0 +1,1705 @@ +# GENERATED FILE — DO NOT EDIT. +# Source: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/6d441518de8a9d5adbab0b10a76a667a63f90665/schema/2025-03-26/schema.json +# Protocol version: 2025-03-26 Generator: datamodel-code-generator 0.57.0 +# Regenerate: uv run --frozen python scripts/update_spec_types.py 2025-03-26 [--sha ] +# pyright: reportIncompatibleVariableOverride=false +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import AnyUrl, Base64Str, ConfigDict, Field + +from tests.spec_oracles._base import OracleModel + + +class BlobResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + blob: Base64Str + """ + A base64-encoded string representing the binary data of the item. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class Params(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + arguments: dict[str, Any] | None = None + name: str + + +class CallToolRequest(OracleModel): + """Used by the client to invoke a tool provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["tools/call"] + params: Params + + +class Roots(OracleModel): + """Present if the client supports listing roots.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether the client supports notifications for changes to the roots list. + """ + + +class ClientCapabilities(OracleModel): + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ + roots: Roots | None = None + """ + Present if the client supports listing roots. + """ + sampling: dict[str, Any] | None = None + """ + Present if the client supports sampling from an LLM. + """ + + +class Argument(OracleModel): + """The argument's information""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + The name of the argument + """ + value: str + """ + The value of the argument to use for completion matching. + """ + + +class Completion(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + has_more: Annotated[bool | None, Field(alias="hasMore")] = None + """ + Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. + """ + total: int | None = None + """ + The total number of completion options available. This can exceed the number of values actually sent in the response. + """ + values: list[str] + """ + An array of completion values. Must not exceed 100 items. + """ + + +class CompleteResult(OracleModel): + """The server's response to a completion/complete request""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + completion: Completion + + +Cursor: TypeAlias = str + + +class Params4(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ + name: str + """ + The name of the prompt or prompt template. + """ + + +class GetPromptRequest(OracleModel): + """Used by the client to get a prompt provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["prompts/get"] + params: Params4 + + +class Implementation(OracleModel): + """Describes the name and version of an MCP implementation.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + version: str + + +class Params5(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + capabilities: ClientCapabilities + client_info: Annotated[Implementation, Field(alias="clientInfo")] + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. + """ + + +class InitializeRequest(OracleModel): + """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["initialize"] + params: Params5 + + +class Params6(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class InitializedNotification(OracleModel): + """This notification is sent from the client to the server after initialization has finished.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/initialized"] + params: Params6 | None = None + + +class Error(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + code: int + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class JSONRPCNotification(OracleModel): + """A notification which does not expect a response.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: str + params: Params6 | None = None + + +class Params9(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class ListPromptsRequest(OracleModel): + """Sent from the client to request a list of prompts and prompt templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["prompts/list"] + params: Params9 | None = None + + +class ListResourceTemplatesRequest(OracleModel): + """Sent from the client to request a list of resource templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/templates/list"] + params: Params9 | None = None + + +class ListResourcesRequest(OracleModel): + """Sent from the client to request a list of resources the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/list"] + params: Params9 | None = None + + +class ListToolsRequest(OracleModel): + """Sent from the client to request a list of tools the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["tools/list"] + params: Params9 | None = None + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +class Params14(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ + level: LoggingLevel + """ + The severity of this log message. + """ + logger: str | None = None + """ + An optional name of the logger issuing this message. + """ + + +class LoggingMessageNotification(OracleModel): + """Notification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/message"] + params: Params14 + + +class ModelHint(OracleModel): + """Hints to use for model selection. + + Keys not declared here are currently left unspecified by the spec and are up + to the client to interpret. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str | None = None + """ + A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` + """ + + +class ModelPreferences(OracleModel): + """The server's preferences for model selection, requested of the client during sampling. + + Because LLMs can vary along multiple dimensions, choosing the "best" model is + rarely straightforward. Different models excel in different areas—some are + faster but less capable, others are more capable but more expensive, and so + on. This interface allows servers to express their priorities across multiple + dimensions to help clients make an appropriate selection for their use case. + + These preferences are always advisory. The client MAY ignore them. It is also + up to the client to decide how to interpret these preferences and how to + balance them against other considerations. + """ + + model_config = ConfigDict( + extra="allow", + ) + cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + """ + hints: list[ModelHint] | None = None + """ + Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. + """ + intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + """ + speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + """ + + +class Params15(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class Notification(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: Params15 | None = None + + +class Params16(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class PaginatedRequest(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: Params16 | None = None + + +class PaginatedResult(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + + +ProgressToken: TypeAlias = str | int + + +class PromptArgument(OracleModel): + """Describes an argument that a prompt can accept.""" + + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + """ + A human-readable description of the argument. + """ + name: str + """ + The name of the argument. + """ + required: bool | None = None + """ + Whether this argument must be provided. + """ + + +class Params19(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class PromptListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/prompts/list_changed"] + params: Params19 | None = None + + +class PromptReference(OracleModel): + """Identifies a prompt.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + The name of the prompt or prompt template + """ + type: Literal["ref/prompt"] + + +class Params20(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class ReadResourceRequest(OracleModel): + """Sent from the client to the server, to read a specific resource URI.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/read"] + params: Params20 + + +class Meta(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + """ + + +class Params21(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class Request(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: Params21 | None = None + + +RequestId: TypeAlias = str | int + + +class ResourceContents(OracleModel): + """The contents of a specific resource or sub-resource.""" + + model_config = ConfigDict( + extra="allow", + ) + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class Params22(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class ResourceListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/resources/list_changed"] + params: Params22 | None = None + + +class ResourceReference(OracleModel): + """A reference to a resource or resource template definition.""" + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["ref/resource"] + uri: str + """ + The URI or URI template of the resource. + """ + + +class Params23(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. + """ + + +class ResourceUpdatedNotification(OracleModel): + """A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/resources/updated"] + params: Params23 + + +class Result(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + + +Role: TypeAlias = Literal["assistant", "user"] + + +class Root(OracleModel): + """Represents a root directory or file that the server can operate on.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ + uri: AnyUrl + """ + The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ + + +class Params24(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class RootsListChangedNotification(OracleModel): + """A notification from the client to the server, informing it that the list of roots has changed. + This notification should be sent whenever the client adds, removes, or modifies any root. + The server should then request an updated list of roots using the ListRootsRequest. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/roots/list_changed"] + params: Params24 | None = None + + +class Prompts(OracleModel): + """Present if the server offers any prompt templates.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the prompt list. + """ + + +class Resources(OracleModel): + """Present if the server offers any resources to read.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the resource list. + """ + subscribe: bool | None = None + """ + Whether this server supports subscribing to resource updates. + """ + + +class Tools(OracleModel): + """Present if the server offers any tools to call.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the tool list. + """ + + +class ServerCapabilities(OracleModel): + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + completions: dict[str, Any] | None = None + """ + Present if the server supports argument autocompletion suggestions. + """ + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ + logging: dict[str, Any] | None = None + """ + Present if the server supports sending log messages to the client. + """ + prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ + resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ + tools: Tools | None = None + """ + Present if the server offers any tools to call. + """ + + +class Params25(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + level: LoggingLevel + """ + The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message. + """ + + +class SetLevelRequest(OracleModel): + """A request from the client to the server, to enable or adjust logging.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["logging/setLevel"] + params: Params25 + + +class Params26(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class SubscribeRequest(OracleModel): + """Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/subscribe"] + params: Params26 + + +class TextResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class InputSchema(OracleModel): + """A JSON Schema object defining the expected parameters for the tool.""" + + model_config = ConfigDict( + extra="allow", + ) + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class ToolAnnotations(OracleModel): + """Additional properties describing a Tool to clients. + + NOTE: all properties in ToolAnnotations are **hints**. + They are not guaranteed to provide a faithful description of + tool behavior (including descriptive properties like `title`). + + Clients should never make tool use decisions based on ToolAnnotations + received from untrusted servers. + """ + + model_config = ConfigDict( + extra="allow", + ) + destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None + """ + If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: true + """ + idempotent_hint: Annotated[bool | None, Field(alias="idempotentHint")] = None + """ + If true, calling the tool repeatedly with the same arguments + will have no additional effect on the its environment. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: false + """ + open_world_hint: Annotated[bool | None, Field(alias="openWorldHint")] = None + """ + If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + + Default: true + """ + read_only_hint: Annotated[bool | None, Field(alias="readOnlyHint")] = None + """ + If true, the tool does not modify its environment. + + Default: false + """ + title: str | None = None + """ + A human-readable title for the tool. + """ + + +class Params27(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. + """ + + +class ToolListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/tools/list_changed"] + params: Params27 | None = None + + +class Params28(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource to unsubscribe from. + """ + + +class UnsubscribeRequest(OracleModel): + """Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/unsubscribe"] + params: Params28 + + +class Annotations(OracleModel): + """Optional annotations for the client. The client can use annotations to inform how objects are used or displayed""" + + model_config = ConfigDict( + extra="allow", + ) + audience: list[Role] | None = None + """ + Describes who the intended customer of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). + """ + priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None + """ + Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + """ + + +class AudioContent(OracleModel): + """Audio provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: Base64Str + """ + The base64-encoded audio data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the audio. Different providers may support different audio types. + """ + type: Literal["audio"] + + +class Params1(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ + request_id: Annotated[RequestId, Field(alias="requestId")] + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + """ + + +class CancelledNotification(OracleModel): + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + + A client MUST NOT attempt to cancel its `initialize` request. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/cancelled"] + params: Params1 + + +class Params2(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + argument: Argument + """ + The argument's information + """ + ref: PromptReference | ResourceReference + + +class CompleteRequest(OracleModel): + """A request from the client to the server, to ask for completion options.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["completion/complete"] + params: Params2 + + +class EmbeddedResource(OracleModel): + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +EmptyResult: TypeAlias = Result + + +class ImageContent(OracleModel): + """An image provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: Base64Str + """ + The base64-encoded image data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ + type: Literal["image"] + + +class InitializeResult(OracleModel): + """After receiving an initialize request from the client, the server sends this response.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + capabilities: ServerCapabilities + instructions: str | None = None + """ + Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. + """ + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class JSONRPCError(OracleModel): + """A response to a request that indicates an error occurred.""" + + model_config = ConfigDict( + extra="allow", + ) + error: Error + id: RequestId + jsonrpc: Literal["2.0"] + + +class Params8(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class JSONRPCRequest(OracleModel): + """A request that expects a response.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: Params8 | None = None + + +class JSONRPCResponse(OracleModel): + """A successful (non-error) response to a request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class Params12(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class ListRootsRequest(OracleModel): + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["roots/list"] + params: Params12 | None = None + + +class ListRootsResult(OracleModel): + """The client's response to a roots/list request from the server. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + roots: list[Root] + + +class Params17(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class PingRequest(OracleModel): + """A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["ping"] + params: Params17 | None = None + + +class Params18(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + message: str | None = None + """ + An optional message describing the current progress. + """ + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """ + The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. + """ + total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ + + +class ProgressNotification(OracleModel): + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/progress"] + params: Params18 + + +class Prompt(OracleModel): + """A prompt or prompt template that the server offers.""" + + model_config = ConfigDict( + extra="allow", + ) + arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ + description: str | None = None + """ + An optional description of what this prompt provides + """ + name: str + """ + The name of the prompt or prompt template. + """ + + +class ReadResourceResult(OracleModel): + """The server's response to a resources/read request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + contents: list[TextResourceContents | BlobResourceContents] + + +class Resource(OracleModel): + """A known resource that the server is capable of reading.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + A human-readable name for this resource. + + This can be used by clients to populate UI elements. + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceTemplate(OracleModel): + """A template description for resources available on the server.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. + """ + name: str + """ + A human-readable name for the type of resource this template refers to. + + This can be used by clients to populate UI elements. + """ + uri_template: Annotated[str, Field(alias="uriTemplate")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification +) + + +class TextContent(OracleModel): + """Text provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + text: str + """ + The text content of the message. + """ + type: Literal["text"] + + +class Tool(OracleModel): + """Definition for a tool the client can call.""" + + model_config = ConfigDict( + extra="allow", + ) + annotations: ToolAnnotations | None = None + """ + Optional additional tool information. + """ + description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model. + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + """ + name: str + """ + The name of the tool. + """ + + +class CallToolResult(OracleModel): + """The server's response to a tool call. + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + content: list[TextContent | ImageContent | AudioContent | EmbeddedResource] + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + """ + + +ClientNotification: TypeAlias = ( + CancelledNotification | InitializedNotification | ProgressNotification | RootsListChangedNotification +) + + +ClientRequest: TypeAlias = ( + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | SetLevelRequest + | CompleteRequest +) + + +class CreateMessageResult(OracleModel): + """The client's response to a sampling/create_message request from the server. The client should inform the user before returning the sampled message, to allow them to inspect the response (human in the loop) and decide whether to allow the server to see it.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + content: TextContent | ImageContent | AudioContent + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + The reason why sampling stopped, if known. + """ + + +JSONRPCBatchRequest: TypeAlias = list[JSONRPCRequest | JSONRPCNotification] +"""A JSON-RPC batch request, as described in https://www.jsonrpc.org/specification#batch.""" + + +JSONRPCBatchResponse: TypeAlias = list[JSONRPCResponse | JSONRPCError] +"""A JSON-RPC batch response, as described in https://www.jsonrpc.org/specification#batch.""" + + +JSONRPCMessage: TypeAlias = ( + JSONRPCRequest + | JSONRPCNotification + | list[JSONRPCRequest | JSONRPCNotification] + | JSONRPCResponse + | JSONRPCError + | list[JSONRPCResponse | JSONRPCError] +) + + +class ListPromptsResult(OracleModel): + """The server's response to a prompts/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + prompts: list[Prompt] + + +class ListResourceTemplatesResult(OracleModel): + """The server's response to a resources/templates/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + + +class ListResourcesResult(OracleModel): + """The server's response to a resources/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resources: list[Resource] + + +class ListToolsResult(OracleModel): + """The server's response to a tools/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tools: list[Tool] + + +class PromptMessage(OracleModel): + """Describes a message returned as part of a prompt. + + This is similar to `SamplingMessage`, but also supports the embedding of + resources from the MCP server. + """ + + model_config = ConfigDict( + extra="allow", + ) + content: TextContent | ImageContent | AudioContent | EmbeddedResource + role: Role + + +class SamplingMessage(OracleModel): + """Describes a message issued to or received from an LLM API.""" + + model_config = ConfigDict( + extra="allow", + ) + content: TextContent | ImageContent | AudioContent + role: Role + + +ClientResult: TypeAlias = Result | CreateMessageResult | ListRootsResult + + +class Params3(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. + """ + max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested. + """ + messages: list[SamplingMessage] + metadata: dict[str, Any] | None = None + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ + temperature: float | None = None + + +class CreateMessageRequest(OracleModel): + """A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["sampling/createMessage"] + params: Params3 + + +class GetPromptResult(OracleModel): + """The server's response to a prompts/get request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. + """ + description: str | None = None + """ + An optional description for the prompt. + """ + messages: list[PromptMessage] + + +ServerRequest: TypeAlias = PingRequest | CreateMessageRequest | ListRootsRequest + + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) + +SPEC_DEFS: tuple[str, ...] = ( + "Annotations", + "AudioContent", + "BlobResourceContents", + "CallToolRequest", + "CallToolResult", + "CancelledNotification", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteResult", + "CreateMessageRequest", + "CreateMessageResult", + "Cursor", + "EmbeddedResource", + "EmptyResult", + "GetPromptRequest", + "GetPromptResult", + "ImageContent", + "Implementation", + "InitializeRequest", + "InitializeResult", + "InitializedNotification", + "JSONRPCBatchRequest", + "JSONRPCBatchResponse", + "JSONRPCError", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "ModelHint", + "ModelPreferences", + "Notification", + "PaginatedRequest", + "PaginatedResult", + "PingRequest", + "ProgressNotification", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceResult", + "Request", + "RequestId", + "Resource", + "ResourceContents", + "ResourceListChangedNotification", + "ResourceReference", + "ResourceTemplate", + "ResourceUpdatedNotification", + "Result", + "Role", + "Root", + "RootsListChangedNotification", + "SamplingMessage", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "SubscribeRequest", + "TextContent", + "TextResourceContents", + "Tool", + "ToolAnnotations", + "ToolListChangedNotification", + "UnsubscribeRequest", +) diff --git a/tests/spec_oracles/v2025_06_18.py b/tests/spec_oracles/v2025_06_18.py new file mode 100644 index 0000000000..174ccada23 --- /dev/null +++ b/tests/spec_oracles/v2025_06_18.py @@ -0,0 +1,2065 @@ +# GENERATED FILE — DO NOT EDIT. +# Source: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/6d441518de8a9d5adbab0b10a76a667a63f90665/schema/2025-06-18/schema.json +# Protocol version: 2025-06-18 Generator: datamodel-code-generator 0.57.0 +# Regenerate: uv run --frozen python scripts/update_spec_types.py 2025-06-18 [--sha ] +# pyright: reportIncompatibleVariableOverride=false +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import AnyUrl, Base64Str, ConfigDict, Field + +from tests.spec_oracles._base import OracleModel + + +class BaseMetadata(OracleModel): + """Base interface for metadata with name (identifier) and title (display name) properties.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class BlobResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + blob: Base64Str + """ + A base64-encoded string representing the binary data of the item. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class BooleanSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + default: bool | None = None + description: str | None = None + title: str | None = None + type: Literal["boolean"] + + +class Params(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + arguments: dict[str, Any] | None = None + name: str + + +class CallToolRequest(OracleModel): + """Used by the client to invoke a tool provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["tools/call"] + params: Params + + +class Roots(OracleModel): + """Present if the client supports listing roots.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether the client supports notifications for changes to the roots list. + """ + + +class ClientCapabilities(OracleModel): + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + elicitation: dict[str, Any] | None = None + """ + Present if the client supports elicitation from the server. + """ + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ + roots: Roots | None = None + """ + Present if the client supports listing roots. + """ + sampling: dict[str, Any] | None = None + """ + Present if the client supports sampling from an LLM. + """ + + +class Argument(OracleModel): + """The argument's information""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + The name of the argument + """ + value: str + """ + The value of the argument to use for completion matching. + """ + + +class Context(OracleModel): + """Additional, optional context for completions""" + + model_config = ConfigDict( + extra="allow", + ) + arguments: dict[str, str] | None = None + """ + Previously-resolved variables in a URI template or prompt. + """ + + +class Completion(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + has_more: Annotated[bool | None, Field(alias="hasMore")] = None + """ + Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. + """ + total: int | None = None + """ + The total number of completion options available. This can exceed the number of values actually sent in the response. + """ + values: list[str] + """ + An array of completion values. Must not exceed 100 items. + """ + + +class CompleteResult(OracleModel): + """The server's response to a completion/complete request""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + completion: Completion + + +Cursor: TypeAlias = str + + +class ElicitResult(OracleModel): + """The client's response to an elicitation request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + action: Literal["accept", "cancel", "decline"] + """ + The user action in response to the elicitation. + - "accept": User submitted the form/confirmed the action + - "decline": User explicitly declined the action + - "cancel": User dismissed without making an explicit choice + """ + content: dict[str, str | int | bool] | None = None + """ + The submitted form data, only present when action is "accept". + Contains values matching the requested schema. + """ + + +class EnumSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + enum: list[str] + enum_names: Annotated[list[str] | None, Field(alias="enumNames")] = None + title: str | None = None + type: Literal["string"] + + +class Params5(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ + name: str + """ + The name of the prompt or prompt template. + """ + + +class GetPromptRequest(OracleModel): + """Used by the client to get a prompt provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["prompts/get"] + params: Params5 + + +class Implementation(OracleModel): + """Describes the name and version of an MCP implementation, with an optional title for UI representation.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + version: str + + +class Params6(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + capabilities: ClientCapabilities + client_info: Annotated[Implementation, Field(alias="clientInfo")] + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. + """ + + +class InitializeRequest(OracleModel): + """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["initialize"] + params: Params6 + + +class Params7(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class InitializedNotification(OracleModel): + """This notification is sent from the client to the server after initialization has finished.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/initialized"] + params: Params7 | None = None + + +class Error(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + code: int + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class JSONRPCNotification(OracleModel): + """A notification which does not expect a response.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: str + params: Params7 | None = None + + +class Params10(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class ListPromptsRequest(OracleModel): + """Sent from the client to request a list of prompts and prompt templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["prompts/list"] + params: Params10 | None = None + + +class ListResourceTemplatesRequest(OracleModel): + """Sent from the client to request a list of resource templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/templates/list"] + params: Params10 | None = None + + +class ListResourcesRequest(OracleModel): + """Sent from the client to request a list of resources the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/list"] + params: Params10 | None = None + + +class ListToolsRequest(OracleModel): + """Sent from the client to request a list of tools the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["tools/list"] + params: Params10 | None = None + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +class Params15(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ + level: LoggingLevel + """ + The severity of this log message. + """ + logger: str | None = None + """ + An optional name of the logger issuing this message. + """ + + +class LoggingMessageNotification(OracleModel): + """Notification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/message"] + params: Params15 + + +class ModelHint(OracleModel): + """Hints to use for model selection. + + Keys not declared here are currently left unspecified by the spec and are up + to the client to interpret. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str | None = None + """ + A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` + """ + + +class ModelPreferences(OracleModel): + """The server's preferences for model selection, requested of the client during sampling. + + Because LLMs can vary along multiple dimensions, choosing the "best" model is + rarely straightforward. Different models excel in different areas—some are + faster but less capable, others are more capable but more expensive, and so + on. This interface allows servers to express their priorities across multiple + dimensions to help clients make an appropriate selection for their use case. + + These preferences are always advisory. The client MAY ignore them. It is also + up to the client to decide how to interpret these preferences and how to + balance them against other considerations. + """ + + model_config = ConfigDict( + extra="allow", + ) + cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + """ + hints: list[ModelHint] | None = None + """ + Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. + """ + intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + """ + speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + """ + + +class Params16(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class Notification(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: Params16 | None = None + + +class NumberSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + maximum: int | None = None + minimum: int | None = None + title: str | None = None + type: Literal["integer", "number"] + + +class Params17(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class PaginatedRequest(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: Params17 | None = None + + +class PaginatedResult(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + + +ProgressToken: TypeAlias = str | int + + +class PromptArgument(OracleModel): + """Describes an argument that a prompt can accept.""" + + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + """ + A human-readable description of the argument. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + required: bool | None = None + """ + Whether this argument must be provided. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class Params20(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class PromptListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/prompts/list_changed"] + params: Params20 | None = None + + +class PromptReference(OracleModel): + """Identifies a prompt.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["ref/prompt"] + + +class Params21(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class ReadResourceRequest(OracleModel): + """Sent from the client to the server, to read a specific resource URI.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/read"] + params: Params21 + + +class Meta(OracleModel): + """See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage.""" + + model_config = ConfigDict( + extra="allow", + ) + progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + """ + + +class Params22(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class Request(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: Params22 | None = None + + +RequestId: TypeAlias = str | int + + +class ResourceContents(OracleModel): + """The contents of a specific resource or sub-resource.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class Params23(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class ResourceListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/resources/list_changed"] + params: Params23 | None = None + + +class ResourceTemplateReference(OracleModel): + """A reference to a resource or resource template definition.""" + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["ref/resource"] + uri: str + """ + The URI or URI template of the resource. + """ + + +class Params24(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. + """ + + +class ResourceUpdatedNotification(OracleModel): + """A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/resources/updated"] + params: Params24 + + +class Result(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +Role: TypeAlias = Literal["assistant", "user"] + + +class Root(OracleModel): + """Represents a root directory or file that the server can operate on.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ + uri: AnyUrl + """ + The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ + + +class Params25(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class RootsListChangedNotification(OracleModel): + """A notification from the client to the server, informing it that the list of roots has changed. + This notification should be sent whenever the client adds, removes, or modifies any root. + The server should then request an updated list of roots using the ListRootsRequest. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/roots/list_changed"] + params: Params25 | None = None + + +class Prompts(OracleModel): + """Present if the server offers any prompt templates.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the prompt list. + """ + + +class Resources(OracleModel): + """Present if the server offers any resources to read.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the resource list. + """ + subscribe: bool | None = None + """ + Whether this server supports subscribing to resource updates. + """ + + +class Tools(OracleModel): + """Present if the server offers any tools to call.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the tool list. + """ + + +class ServerCapabilities(OracleModel): + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + completions: dict[str, Any] | None = None + """ + Present if the server supports argument autocompletion suggestions. + """ + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ + logging: dict[str, Any] | None = None + """ + Present if the server supports sending log messages to the client. + """ + prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ + resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ + tools: Tools | None = None + """ + Present if the server offers any tools to call. + """ + + +class Params26(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + level: LoggingLevel + """ + The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message. + """ + + +class SetLevelRequest(OracleModel): + """A request from the client to the server, to enable or adjust logging.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["logging/setLevel"] + params: Params26 + + +class StringSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + format: Literal["date", "date-time", "email", "uri"] | None = None + max_length: Annotated[int | None, Field(alias="maxLength")] = None + min_length: Annotated[int | None, Field(alias="minLength")] = None + title: str | None = None + type: Literal["string"] + + +class Params27(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class SubscribeRequest(OracleModel): + """Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/subscribe"] + params: Params27 + + +class TextResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class InputSchema(OracleModel): + """A JSON Schema object defining the expected parameters for the tool.""" + + model_config = ConfigDict( + extra="allow", + ) + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class OutputSchema(OracleModel): + """An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + """ + + model_config = ConfigDict( + extra="allow", + ) + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class ToolAnnotations(OracleModel): + """Additional properties describing a Tool to clients. + + NOTE: all properties in ToolAnnotations are **hints**. + They are not guaranteed to provide a faithful description of + tool behavior (including descriptive properties like `title`). + + Clients should never make tool use decisions based on ToolAnnotations + received from untrusted servers. + """ + + model_config = ConfigDict( + extra="allow", + ) + destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None + """ + If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: true + """ + idempotent_hint: Annotated[bool | None, Field(alias="idempotentHint")] = None + """ + If true, calling the tool repeatedly with the same arguments + will have no additional effect on the its environment. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: false + """ + open_world_hint: Annotated[bool | None, Field(alias="openWorldHint")] = None + """ + If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + + Default: true + """ + read_only_hint: Annotated[bool | None, Field(alias="readOnlyHint")] = None + """ + If true, the tool does not modify its environment. + + Default: false + """ + title: str | None = None + """ + A human-readable title for the tool. + """ + + +class Params28(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class ToolListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/tools/list_changed"] + params: Params28 | None = None + + +class Params29(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + uri: AnyUrl + """ + The URI of the resource to unsubscribe from. + """ + + +class UnsubscribeRequest(OracleModel): + """Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["resources/unsubscribe"] + params: Params29 + + +class Annotations(OracleModel): + """Optional annotations for the client. The client can use annotations to inform how objects are used or displayed""" + + model_config = ConfigDict( + extra="allow", + ) + audience: list[Role] | None = None + """ + Describes who the intended customer of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). + """ + last_modified: Annotated[str | None, Field(alias="lastModified")] = None + """ + The moment the resource was last modified, as an ISO 8601 formatted string. + + Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + + Examples: last activity timestamp in an open file, timestamp when the resource + was attached, etc. + """ + priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None + """ + Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + """ + + +class AudioContent(OracleModel): + """Audio provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: Base64Str + """ + The base64-encoded audio data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the audio. Different providers may support different audio types. + """ + type: Literal["audio"] + + +class Params1(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ + request_id: Annotated[RequestId, Field(alias="requestId")] + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + """ + + +class CancelledNotification(OracleModel): + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + + A client MUST NOT attempt to cancel its `initialize` request. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/cancelled"] + params: Params1 + + +class Params2(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + argument: Argument + """ + The argument's information + """ + context: Context | None = None + """ + Additional, optional context for completions + """ + ref: PromptReference | ResourceTemplateReference + + +class CompleteRequest(OracleModel): + """A request from the client to the server, to ask for completion options.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["completion/complete"] + params: Params2 + + +class EmbeddedResource(OracleModel): + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +EmptyResult: TypeAlias = Result + + +class ImageContent(OracleModel): + """An image provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: Base64Str + """ + The base64-encoded image data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ + type: Literal["image"] + + +class InitializeResult(OracleModel): + """After receiving an initialize request from the client, the server sends this response.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + capabilities: ServerCapabilities + instructions: str | None = None + """ + Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. + """ + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class JSONRPCError(OracleModel): + """A response to a request that indicates an error occurred.""" + + model_config = ConfigDict( + extra="allow", + ) + error: Error + id: RequestId + jsonrpc: Literal["2.0"] + + +class Params9(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class JSONRPCRequest(OracleModel): + """A request that expects a response.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: Params9 | None = None + + +class JSONRPCResponse(OracleModel): + """A successful (non-error) response to a request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class Params13(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class ListRootsRequest(OracleModel): + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["roots/list"] + params: Params13 | None = None + + +class ListRootsResult(OracleModel): + """The client's response to a roots/list request from the server. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + roots: list[Root] + + +class Params18(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class PingRequest(OracleModel): + """A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["ping"] + params: Params18 | None = None + + +PrimitiveSchemaDefinition: TypeAlias = StringSchema | NumberSchema | BooleanSchema | EnumSchema + + +class Params19(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + message: str | None = None + """ + An optional message describing the current progress. + """ + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """ + The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. + """ + total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ + + +class ProgressNotification(OracleModel): + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["notifications/progress"] + params: Params19 + + +class Prompt(OracleModel): + """A prompt or prompt template that the server offers.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ + description: str | None = None + """ + An optional description of what this prompt provides + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class ReadResourceResult(OracleModel): + """The server's response to a resources/read request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + contents: list[TextResourceContents | BlobResourceContents] + + +class Resource(OracleModel): + """A known resource that the server is capable of reading.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceLink(OracleModel): + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["resource_link"] + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceTemplate(OracleModel): + """A template description for resources available on the server.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri_template: Annotated[str, Field(alias="uriTemplate")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification +) + + +class TextContent(OracleModel): + """Text provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + text: str + """ + The text content of the message. + """ + type: Literal["text"] + + +class Tool(OracleModel): + """Definition for a tool the client can call.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + annotations: ToolAnnotations | None = None + """ + Optional additional tool information. + + Display name precedence order is: title, annotations.title, then name. + """ + description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model. + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + """ + An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +ClientNotification: TypeAlias = ( + CancelledNotification | InitializedNotification | ProgressNotification | RootsListChangedNotification +) + + +ClientRequest: TypeAlias = ( + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | SetLevelRequest + | CompleteRequest +) + + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + + +class CreateMessageResult(OracleModel): + """The client's response to a sampling/create_message request from the server. The client should inform the user before returning the sampled message, to allow them to inspect the response (human in the loop) and decide whether to allow the server to see it.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + content: TextContent | ImageContent | AudioContent + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + The reason why sampling stopped, if known. + """ + + +class RequestedSchema(OracleModel): + """A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + model_config = ConfigDict( + extra="allow", + ) + properties: dict[str, PrimitiveSchemaDefinition] + required: list[str] | None = None + type: Literal["object"] + + +class Params4(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + message: str + """ + The message to present to the user. + """ + requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + """ + A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + +class ElicitRequest(OracleModel): + """A request from the server to elicit additional information from the user via the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["elicitation/create"] + params: Params4 + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError + + +class ListPromptsResult(OracleModel): + """The server's response to a prompts/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + prompts: list[Prompt] + + +class ListResourceTemplatesResult(OracleModel): + """The server's response to a resources/templates/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + + +class ListResourcesResult(OracleModel): + """The server's response to a resources/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resources: list[Resource] + + +class ListToolsResult(OracleModel): + """The server's response to a tools/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tools: list[Tool] + + +class PromptMessage(OracleModel): + """Describes a message returned as part of a prompt. + + This is similar to `SamplingMessage`, but also supports the embedding of + resources from the MCP server. + """ + + model_config = ConfigDict( + extra="allow", + ) + content: ContentBlock + role: Role + + +class SamplingMessage(OracleModel): + """Describes a message issued to or received from an LLM API.""" + + model_config = ConfigDict( + extra="allow", + ) + content: TextContent | ImageContent | AudioContent + role: Role + + +class CallToolResult(OracleModel): + """The server's response to a tool call.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + content: list[ContentBlock] + """ + A list of content objects that represent the unstructured result of the tool call. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ + structured_content: Annotated[dict[str, Any] | None, Field(alias="structuredContent")] = None + """ + An optional JSON object that represents the structured result of the tool call. + """ + + +ClientResult: TypeAlias = Result | CreateMessageResult | ListRootsResult | ElicitResult + + +class Params3(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. + """ + max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. + """ + messages: list[SamplingMessage] + metadata: dict[str, Any] | None = None + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ + temperature: float | None = None + + +class CreateMessageRequest(OracleModel): + """A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["sampling/createMessage"] + params: Params3 + + +class GetPromptResult(OracleModel): + """The server's response to a prompts/get request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + description: str | None = None + """ + An optional description for the prompt. + """ + messages: list[PromptMessage] + + +ServerRequest: TypeAlias = PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest + + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) + +SPEC_DEFS: tuple[str, ...] = ( + "Annotations", + "AudioContent", + "BaseMetadata", + "BlobResourceContents", + "BooleanSchema", + "CallToolRequest", + "CallToolResult", + "CancelledNotification", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteResult", + "ContentBlock", + "CreateMessageRequest", + "CreateMessageResult", + "Cursor", + "ElicitRequest", + "ElicitResult", + "EmbeddedResource", + "EmptyResult", + "EnumSchema", + "GetPromptRequest", + "GetPromptResult", + "ImageContent", + "Implementation", + "InitializeRequest", + "InitializeResult", + "InitializedNotification", + "JSONRPCError", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "ModelHint", + "ModelPreferences", + "Notification", + "NumberSchema", + "PaginatedRequest", + "PaginatedResult", + "PingRequest", + "PrimitiveSchemaDefinition", + "ProgressNotification", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceResult", + "Request", + "RequestId", + "Resource", + "ResourceContents", + "ResourceLink", + "ResourceListChangedNotification", + "ResourceTemplate", + "ResourceTemplateReference", + "ResourceUpdatedNotification", + "Result", + "Role", + "Root", + "RootsListChangedNotification", + "SamplingMessage", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "StringSchema", + "SubscribeRequest", + "TextContent", + "TextResourceContents", + "Tool", + "ToolAnnotations", + "ToolListChangedNotification", + "UnsubscribeRequest", +) diff --git a/tests/spec_oracles/v2025_11_25.py b/tests/spec_oracles/v2025_11_25.py new file mode 100644 index 0000000000..aa61acdc43 --- /dev/null +++ b/tests/spec_oracles/v2025_11_25.py @@ -0,0 +1,3256 @@ +# GENERATED FILE — DO NOT EDIT. +# Source: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/6d441518de8a9d5adbab0b10a76a667a63f90665/schema/2025-11-25/schema.json +# Protocol version: 2025-11-25 Generator: datamodel-code-generator 0.57.0 +# Regenerate: uv run --frozen python scripts/update_spec_types.py 2025-11-25 [--sha ] +# pyright: reportIncompatibleVariableOverride=false +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import AnyUrl, Base64Str, ConfigDict, Field + +from tests.spec_oracles._base import OracleModel + + +class BaseMetadata(OracleModel): + """Base interface for metadata with name (identifier) and title (display name) properties.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class BlobResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + blob: Base64Str + """ + A base64-encoded string representing the binary data of the item. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class BooleanSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + default: bool | None = None + description: str | None = None + title: str | None = None + type: Literal["boolean"] + + +class Params(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier to cancel. + """ + + +class Elicitation(OracleModel): + """Present if the client supports elicitation from the server.""" + + model_config = ConfigDict( + extra="allow", + ) + form: dict[str, Any] | None = None + url: dict[str, Any] | None = None + + +class Roots(OracleModel): + """Present if the client supports listing roots.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether the client supports notifications for changes to the roots list. + """ + + +class Sampling(OracleModel): + """Present if the client supports sampling from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + context: dict[str, Any] | None = None + """ + Whether the client supports context inclusion via includeContext parameter. + If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). + """ + tools: dict[str, Any] | None = None + """ + Whether the client supports tool use via tools and toolChoice parameters. + """ + + +class Elicitation1(OracleModel): + """Task support for elicitation-related requests.""" + + model_config = ConfigDict( + extra="allow", + ) + create: dict[str, Any] | None = None + """ + Whether the client supports task-augmented elicitation/create requests. + """ + + +class Sampling1(OracleModel): + """Task support for sampling-related requests.""" + + model_config = ConfigDict( + extra="allow", + ) + create_message: Annotated[dict[str, Any] | None, Field(alias="createMessage")] = None + """ + Whether the client supports task-augmented sampling/createMessage requests. + """ + + +class Requests(OracleModel): + """Specifies which request types can be augmented with tasks.""" + + model_config = ConfigDict( + extra="allow", + ) + elicitation: Elicitation1 | None = None + """ + Task support for elicitation-related requests. + """ + sampling: Sampling1 | None = None + """ + Task support for sampling-related requests. + """ + + +class Tasks(OracleModel): + """Present if the client supports task-augmented requests.""" + + model_config = ConfigDict( + extra="allow", + ) + cancel: dict[str, Any] | None = None + """ + Whether this client supports tasks/cancel. + """ + list: dict[str, Any] | None = None + """ + Whether this client supports tasks/list. + """ + requests: Requests | None = None + """ + Specifies which request types can be augmented with tasks. + """ + + +class ClientCapabilities(OracleModel): + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + elicitation: Elicitation | None = None + """ + Present if the client supports elicitation from the server. + """ + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ + roots: Roots | None = None + """ + Present if the client supports listing roots. + """ + sampling: Sampling | None = None + """ + Present if the client supports sampling from an LLM. + """ + tasks: Tasks | None = None + """ + Present if the client supports task-augmented requests. + """ + + +class Argument(OracleModel): + """The argument's information""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + The name of the argument + """ + value: str + """ + The value of the argument to use for completion matching. + """ + + +class Context(OracleModel): + """Additional, optional context for completions""" + + model_config = ConfigDict( + extra="allow", + ) + arguments: dict[str, str] | None = None + """ + Previously-resolved variables in a URI template or prompt. + """ + + +class Completion(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + has_more: Annotated[bool | None, Field(alias="hasMore")] = None + """ + Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. + """ + total: int | None = None + """ + The total number of completion options available. This can exceed the number of values actually sent in the response. + """ + values: list[str] + """ + An array of completion values. Must not exceed 100 items. + """ + + +class CompleteResult(OracleModel): + """The server's response to a completion/complete request""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + completion: Completion + + +Cursor: TypeAlias = str + + +class ElicitResult(OracleModel): + """The client's response to an elicitation request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + action: Literal["accept", "cancel", "decline"] + """ + The user action in response to the elicitation. + - "accept": User submitted the form/confirmed the action + - "decline": User explicitly decline the action + - "cancel": User dismissed without making an explicit choice + """ + content: dict[str, list[str] | str | int | bool] | None = None + """ + The submitted form data, only present when action is "accept" and mode was "form". + Contains values matching the requested schema. + Omitted for out-of-band mode responses. + """ + + +class Params1(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ + The ID of the elicitation that completed. + """ + + +class ElicitationCompleteNotification(OracleModel): + """An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/elicitation/complete"] + params: Params1 + + +class Error(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + code: int + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class Params2(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier to retrieve results for. + """ + + +class GetTaskPayloadResult(OracleModel): + """The response to a tasks/result request. + The structure matches the result type of the original request. + For example, a tools/call task would return the CallToolResult structure. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + + +class Params3(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier to query. + """ + + +class Icon(OracleModel): + """An optionally-sized icon that can be displayed in a user interface.""" + + model_config = ConfigDict( + extra="allow", + ) + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + Optional MIME type override if the source MIME type is missing or generic. + For example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`. + """ + sizes: list[str] | None = None + """ + Optional array of strings that specify sizes at which the icon can be used. + Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + + If not provided, the client should assume that the icon can be used at any size. + """ + src: AnyUrl + """ + A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a + `data:` URI with Base64-encoded image data. + + Consumers SHOULD takes steps to ensure URLs serving icons are from the + same domain as the client/server or a trusted domain. + + Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain + executable JavaScript. + """ + theme: Literal["dark", "light"] | None = None + """ + Optional specifier for the theme this icon is designed for. `light` indicates + the icon is designed to be used with a light background, and `dark` indicates + the icon is designed to be used with a dark background. + + If not provided, the client should assume the icon can be used with any theme. + """ + + +class Icons(OracleModel): + """Base interface to add `icons` property.""" + + model_config = ConfigDict( + extra="allow", + ) + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + + +class Implementation(OracleModel): + """Describes the MCP implementation.""" + + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + """ + An optional human-readable description of what this implementation does. + + This can be used by clients or servers to provide context about their purpose + and capabilities. For example, a server might describe the types of resources + or tools it provides, while a client might describe its intended use case. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + version: str + website_url: Annotated[AnyUrl | None, Field(alias="websiteUrl")] = None + """ + An optional URL of the website for this implementation. + """ + + +class JSONRPCNotification(OracleModel): + """A notification which does not expect a response.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class LegacyTitledEnumSchema(OracleModel): + """Use TitledSingleSelectEnumSchema instead. + This interface will be removed in a future version. + """ + + model_config = ConfigDict( + extra="allow", + ) + default: str | None = None + description: str | None = None + enum: list[str] + enum_names: Annotated[list[str] | None, Field(alias="enumNames")] = None + """ + (Legacy) Display names for enum values. + Non-standard according to JSON schema 2020-12. + """ + title: str | None = None + type: Literal["string"] + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +class LoggingMessageNotificationParams(OracleModel): + """Parameters for a `notifications/message` notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ + level: LoggingLevel + """ + The severity of this log message. + """ + logger: str | None = None + """ + An optional name of the logger issuing this message. + """ + + +class ModelHint(OracleModel): + """Hints to use for model selection. + + Keys not declared here are currently left unspecified by the spec and are up + to the client to interpret. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str | None = None + """ + A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` + """ + + +class ModelPreferences(OracleModel): + """The server's preferences for model selection, requested of the client during sampling. + + Because LLMs can vary along multiple dimensions, choosing the "best" model is + rarely straightforward. Different models excel in different areas—some are + faster but less capable, others are more capable but more expensive, and so + on. This interface allows servers to express their priorities across multiple + dimensions to help clients make an appropriate selection for their use case. + + These preferences are always advisory. The client MAY ignore them. It is also + up to the client to decide how to interpret these preferences and how to + balance them against other considerations. + """ + + model_config = ConfigDict( + extra="allow", + ) + cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + """ + hints: list[ModelHint] | None = None + """ + Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. + """ + intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + """ + speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + """ + + +class Notification(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: dict[str, Any] | None = None + + +class NotificationParams(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + + +class NumberSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + default: int | None = None + description: str | None = None + maximum: int | None = None + minimum: int | None = None + title: str | None = None + type: Literal["integer", "number"] + + +class PaginatedResult(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + + +ProgressToken: TypeAlias = str | int + + +class PromptArgument(OracleModel): + """Describes an argument that a prompt can accept.""" + + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + """ + A human-readable description of the argument. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + required: bool | None = None + """ + Whether this argument must be provided. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class PromptListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/prompts/list_changed"] + params: NotificationParams | None = None + + +class PromptReference(OracleModel): + """Identifies a prompt.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["ref/prompt"] + + +class Meta(OracleModel): + """See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.""" + + model_config = ConfigDict( + extra="allow", + ) + progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + """ + + +class ReadResourceRequestParams(OracleModel): + """Parameters for a `resources/read` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: AnyUrl + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class RelatedTaskMetadata(OracleModel): + """Metadata for associating messages with a task. + Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. + """ + + model_config = ConfigDict( + extra="allow", + ) + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier this message is associated with. + """ + + +class Request(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: dict[str, Any] | None = None + + +RequestId: TypeAlias = str | int + + +class RequestParams(OracleModel): + """Common params for any request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + + +class ResourceContents(OracleModel): + """The contents of a specific resource or sub-resource.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/list_changed"] + params: NotificationParams | None = None + + +class ResourceRequestParams(OracleModel): + """Common parameters when working with resources.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: AnyUrl + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class ResourceTemplateReference(OracleModel): + """A reference to a resource or resource template definition.""" + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["ref/resource"] + uri: str + """ + The URI or URI template of the resource. + """ + + +class ResourceUpdatedNotificationParams(OracleModel): + """Parameters for a `notifications/resources/updated` notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: AnyUrl + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. + """ + + +class Result(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + + +Role: TypeAlias = Literal["assistant", "user"] + + +class Root(OracleModel): + """Represents a root directory or file that the server can operate on.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ + uri: AnyUrl + """ + The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ + + +class RootsListChangedNotification(OracleModel): + """A notification from the client to the server, informing it that the list of roots has changed. + This notification should be sent whenever the client adds, removes, or modifies any root. + The server should then request an updated list of roots using the ListRootsRequest. + """ + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/roots/list_changed"] + params: NotificationParams | None = None + + +class Prompts(OracleModel): + """Present if the server offers any prompt templates.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the prompt list. + """ + + +class Resources(OracleModel): + """Present if the server offers any resources to read.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the resource list. + """ + subscribe: bool | None = None + """ + Whether this server supports subscribing to resource updates. + """ + + +class Tools(OracleModel): + """Task support for tool-related requests.""" + + model_config = ConfigDict( + extra="allow", + ) + call: dict[str, Any] | None = None + """ + Whether the server supports task-augmented tools/call requests. + """ + + +class Requests1(OracleModel): + """Specifies which request types can be augmented with tasks.""" + + model_config = ConfigDict( + extra="allow", + ) + tools: Tools | None = None + """ + Task support for tool-related requests. + """ + + +class Tasks1(OracleModel): + """Present if the server supports task-augmented requests.""" + + model_config = ConfigDict( + extra="allow", + ) + cancel: dict[str, Any] | None = None + """ + Whether this server supports tasks/cancel. + """ + list: dict[str, Any] | None = None + """ + Whether this server supports tasks/list. + """ + requests: Requests1 | None = None + """ + Specifies which request types can be augmented with tasks. + """ + + +class Tools1(OracleModel): + """Present if the server offers any tools to call.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the tool list. + """ + + +class ServerCapabilities(OracleModel): + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + completions: dict[str, Any] | None = None + """ + Present if the server supports argument autocompletion suggestions. + """ + experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ + logging: dict[str, Any] | None = None + """ + Present if the server supports sending log messages to the client. + """ + prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ + resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ + tasks: Tasks1 | None = None + """ + Present if the server supports task-augmented requests. + """ + tools: Tools1 | None = None + """ + Present if the server offers any tools to call. + """ + + +class SetLevelRequestParams(OracleModel): + """Parameters for a `logging/setLevel` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + level: LoggingLevel + """ + The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message. + """ + + +class StringSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + default: str | None = None + description: str | None = None + format: Literal["date", "date-time", "email", "uri"] | None = None + max_length: Annotated[int | None, Field(alias="maxLength")] = None + min_length: Annotated[int | None, Field(alias="minLength")] = None + title: str | None = None + type: Literal["string"] + + +class SubscribeRequestParams(OracleModel): + """Parameters for a `resources/subscribe` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: AnyUrl + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class TaskMetadata(OracleModel): + """Metadata for augmenting a request with task execution. + Include this in the `task` field of the request parameters. + """ + + model_config = ConfigDict( + extra="allow", + ) + ttl: int | None = None + """ + Requested duration in milliseconds to retain task from creation. + """ + + +TaskStatus: TypeAlias = Literal["cancelled", "completed", "failed", "input_required", "working"] + + +class TextResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class AnyOfItem(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + const: str + """ + The constant enum value. + """ + title: str + """ + Display title for this option. + """ + + +class Items(OracleModel): + """Schema for array items with enum options and display labels.""" + + model_config = ConfigDict( + extra="allow", + ) + any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] + """ + Array of enum options with values and display labels. + """ + + +class TitledMultiSelectEnumSchema(OracleModel): + """Schema for multiple-selection enumeration with display titles for each option.""" + + model_config = ConfigDict( + extra="allow", + ) + default: list[str] | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + items: Items + """ + Schema for array items with enum options and display labels. + """ + max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ + min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["array"] + + +class OneOfItem(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + const: str + """ + The enum value. + """ + title: str + """ + Display label for this option. + """ + + +class TitledSingleSelectEnumSchema(OracleModel): + """Schema for single-selection enumeration with display titles for each option.""" + + model_config = ConfigDict( + extra="allow", + ) + default: str | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + one_of: Annotated[list[OneOfItem], Field(alias="oneOf")] + """ + Array of enum options with values and display labels. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["string"] + + +class InputSchema(OracleModel): + """A JSON Schema object defining the expected parameters for the tool.""" + + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class OutputSchema(OracleModel): + """An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + + Defaults to JSON Schema 2020-12 when no explicit $schema is provided. + Currently restricted to type: "object" at the root level. + """ + + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class ToolAnnotations(OracleModel): + """Additional properties describing a Tool to clients. + + NOTE: all properties in ToolAnnotations are **hints**. + They are not guaranteed to provide a faithful description of + tool behavior (including descriptive properties like `title`). + + Clients should never make tool use decisions based on ToolAnnotations + received from untrusted servers. + """ + + model_config = ConfigDict( + extra="allow", + ) + destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None + """ + If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: true + """ + idempotent_hint: Annotated[bool | None, Field(alias="idempotentHint")] = None + """ + If true, calling the tool repeatedly with the same arguments + will have no additional effect on its environment. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: false + """ + open_world_hint: Annotated[bool | None, Field(alias="openWorldHint")] = None + """ + If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + + Default: true + """ + read_only_hint: Annotated[bool | None, Field(alias="readOnlyHint")] = None + """ + If true, the tool does not modify its environment. + + Default: false + """ + title: str | None = None + """ + A human-readable title for the tool. + """ + + +class ToolChoice(OracleModel): + """Controls tool selection behavior for sampling requests.""" + + model_config = ConfigDict( + extra="allow", + ) + mode: Literal["auto", "none", "required"] | None = None + """ + Controls the tool use ability of the model: + - "auto": Model decides whether to use tools (default) + - "required": Model MUST use at least one tool before completing + - "none": Model MUST NOT use any tools + """ + + +class ToolExecution(OracleModel): + """Execution-related properties for a tool.""" + + model_config = ConfigDict( + extra="allow", + ) + task_support: Annotated[Literal["forbidden", "optional", "required"] | None, Field(alias="taskSupport")] = None + """ + Indicates whether this tool supports task-augmented execution. + This allows clients to handle long-running operations through polling + the task system. + + - "forbidden": Tool does not support task-augmented execution (default when absent) + - "optional": Tool may support task-augmented execution + - "required": Tool requires task-augmented execution + + Default: "forbidden" + """ + + +class ToolListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/tools/list_changed"] + params: NotificationParams | None = None + + +class ToolUseContent(OracleModel): + """A request from the assistant to call a tool.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool use. Clients SHOULD preserve this field when + including tool uses in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + id: str + """ + A unique identifier for this tool use. + + This ID is used to match tool results to their corresponding tool uses. + """ + input: dict[str, Any] + """ + The arguments to pass to the tool, conforming to the tool's input schema. + """ + name: str + """ + The name of the tool to call. + """ + type: Literal["tool_use"] + + +class UnsubscribeRequestParams(OracleModel): + """Parameters for a `resources/unsubscribe` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + uri: AnyUrl + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class Items1(OracleModel): + """Schema for the array items.""" + + model_config = ConfigDict( + extra="allow", + ) + enum: list[str] + """ + Array of enum values to choose from. + """ + type: Literal["string"] + + +class UntitledMultiSelectEnumSchema(OracleModel): + """Schema for multiple-selection enumeration without display titles for options.""" + + model_config = ConfigDict( + extra="allow", + ) + default: list[str] | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + items: Items1 + """ + Schema for the array items. + """ + max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ + min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["array"] + + +class UntitledSingleSelectEnumSchema(OracleModel): + """Schema for single-selection enumeration without display titles for options.""" + + model_config = ConfigDict( + extra="allow", + ) + default: str | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + enum: list[str] + """ + Array of enum values to choose from. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["string"] + + +class Annotations(OracleModel): + """Optional annotations for the client. The client can use annotations to inform how objects are used or displayed""" + + model_config = ConfigDict( + extra="allow", + ) + audience: list[Role] | None = None + """ + Describes who the intended audience of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). + """ + last_modified: Annotated[str | None, Field(alias="lastModified")] = None + """ + The moment the resource was last modified, as an ISO 8601 formatted string. + + Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + + Examples: last activity timestamp in an open file, timestamp when the resource + was attached, etc. + """ + priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None + """ + Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + """ + + +class AudioContent(OracleModel): + """Audio provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: Base64Str + """ + The base64-encoded audio data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the audio. Different providers may support different audio types. + """ + type: Literal["audio"] + + +class CallToolRequestParams(OracleModel): + """Parameters for a `tools/call` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + arguments: dict[str, Any] | None = None + """ + Arguments to use for the tool call. + """ + name: str + """ + The name of the tool. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + + +class CancelTaskRequest(OracleModel): + """A request to cancel a task.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/cancel"] + params: Params + + +class CancelledNotificationParams(OracleModel): + """Parameters for a `notifications/cancelled` notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ + request_id: Annotated[RequestId | None, Field(alias="requestId")] = None + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + This MUST be provided for cancelling non-task requests. + This MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead). + """ + + +class CompleteRequestParams(OracleModel): + """Parameters for a `completion/complete` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + argument: Argument + """ + The argument's information + """ + context: Context | None = None + """ + Additional, optional context for completions + """ + ref: PromptReference | ResourceTemplateReference + + +class ElicitRequestURLParams(OracleModel): + """The parameters for a request to elicit information from the user via a URL in the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ + The ID of the elicitation, which must be unique within the context of the server. + The client MUST treat this ID as an opaque value. + """ + message: str + """ + The message to present to the user explaining why the interaction is needed. + """ + mode: Literal["url"] + """ + The elicitation mode. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + url: AnyUrl + """ + The URL that the user should navigate to. + """ + + +class EmbeddedResource(OracleModel): + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +EmptyResult: TypeAlias = Result + + +EnumSchema: TypeAlias = ( + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +class GetPromptRequestParams(OracleModel): + """Parameters for a `prompts/get` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ + name: str + """ + The name of the prompt or prompt template. + """ + + +class GetTaskPayloadRequest(OracleModel): + """A request to retrieve the result of a completed task.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/result"] + params: Params2 + + +class GetTaskRequest(OracleModel): + """A request to retrieve the state of a task.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/get"] + params: Params3 + + +class ImageContent(OracleModel): + """An image provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: Base64Str + """ + The base64-encoded image data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ + type: Literal["image"] + + +class InitializeRequestParams(OracleModel): + """Parameters for an `initialize` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + capabilities: ClientCapabilities + client_info: Annotated[Implementation, Field(alias="clientInfo")] + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. + """ + + +class InitializeResult(OracleModel): + """After receiving an initialize request from the client, the server sends this response.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + capabilities: ServerCapabilities + instructions: str | None = None + """ + Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. + """ + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """ + The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class InitializedNotification(OracleModel): + """This notification is sent from the client to the server after initialization has finished.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/initialized"] + params: NotificationParams | None = None + + +class JSONRPCErrorResponse(OracleModel): + """A response to a request that indicates an error occurred.""" + + model_config = ConfigDict( + extra="allow", + ) + error: Error + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class JSONRPCRequest(OracleModel): + """A request that expects a response.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class JSONRPCResultResponse(OracleModel): + """A successful (non-error) response to a request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class ListRootsRequest(OracleModel): + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["roots/list"] + params: RequestParams | None = None + + +class ListRootsResult(OracleModel): + """The client's response to a roots/list request from the server. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + roots: list[Root] + + +class LoggingMessageNotification(OracleModel): + """JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/message"] + params: LoggingMessageNotificationParams + + +MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema + + +class PaginatedRequestParams(OracleModel): + """Common parameters for paginated requests.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class PingRequest(OracleModel): + """A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["ping"] + params: RequestParams | None = None + + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +class ProgressNotificationParams(OracleModel): + """Parameters for a `notifications/progress` notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + message: str | None = None + """ + An optional message describing the current progress. + """ + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """ + The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. + """ + total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ + + +class Prompt(OracleModel): + """A prompt or prompt template that the server offers.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ + description: str | None = None + """ + An optional description of what this prompt provides + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class ReadResourceRequest(OracleModel): + """Sent from the client to the server, to read a specific resource URI.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/read"] + params: ReadResourceRequestParams + + +class ReadResourceResult(OracleModel): + """The server's response to a resources/read request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + contents: list[TextResourceContents | BlobResourceContents] + + +class Resource(OracleModel): + """A known resource that the server is capable of reading.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceLink(OracleModel): + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["resource_link"] + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceTemplate(OracleModel): + """A template description for resources available on the server.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri_template: Annotated[str, Field(alias="uriTemplate")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +class ResourceUpdatedNotification(OracleModel): + """A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/updated"] + params: ResourceUpdatedNotificationParams + + +class SetLevelRequest(OracleModel): + """A request from the client to the server, to enable or adjust logging.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["logging/setLevel"] + params: SetLevelRequestParams + + +SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema + + +class SubscribeRequest(OracleModel): + """Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/subscribe"] + params: SubscribeRequestParams + + +class Task(OracleModel): + """Data associated with a task.""" + + model_config = ConfigDict( + extra="allow", + ) + created_at: Annotated[str, Field(alias="createdAt")] + """ + ISO 8601 timestamp when the task was created. + """ + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + """ + ISO 8601 timestamp when the task was last updated. + """ + poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None + """ + Suggested polling interval in milliseconds. + """ + status: TaskStatus + """ + Current task state. + """ + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ + task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier. + """ + ttl: int | None + """ + Actual retention duration from creation in milliseconds, null for unlimited. + """ + + +class TaskAugmentedRequestParams(OracleModel): + """Common params for any task-augmented request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + + +class TaskStatusNotificationParams(NotificationParams, Task): + """Parameters for a `notifications/tasks/status` notification.""" + + model_config = ConfigDict( + extra="allow", + ) + + +class TextContent(OracleModel): + """Text provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + text: str + """ + The text content of the message. + """ + type: Literal["text"] + + +class Tool(OracleModel): + """Definition for a tool the client can call.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + annotations: ToolAnnotations | None = None + """ + Optional additional tool information. + + Display name precedence order is: title, annotations.title, then name. + """ + description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model. + """ + execution: ToolExecution | None = None + """ + Execution-related properties for this tool. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + """ + An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + + Defaults to JSON Schema 2020-12 when no explicit $schema is provided. + Currently restricted to type: "object" at the root level. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class Data(OracleModel): + """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.).""" + + model_config = ConfigDict( + extra="allow", + ) + elicitations: list[ElicitRequestURLParams] + + +class Error1(Error): + model_config = ConfigDict( + extra="allow", + ) + code: Literal[-32042] + """ + The error type that occurred. + """ + data: Data + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + + +class URLElicitationRequiredError(OracleModel): + """An error response that indicates that the server requires the client to provide additional information via an elicitation request.""" + + model_config = ConfigDict( + extra="allow", + ) + error: Error1 + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class UnsubscribeRequest(OracleModel): + """Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/unsubscribe"] + params: UnsubscribeRequestParams + + +class CallToolRequest(OracleModel): + """Used by the client to invoke a tool provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/call"] + params: CallToolRequestParams + + +class CancelTaskResult(Result, Task): + """The response to a tasks/cancel request.""" + + model_config = ConfigDict( + extra="allow", + ) + + +class CancelledNotification(OracleModel): + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + + A client MUST NOT attempt to cancel its `initialize` request. + + For task cancellation, use the `tasks/cancel` request instead of this notification. + """ + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/cancelled"] + params: CancelledNotificationParams + + +class CompleteRequest(OracleModel): + """A request from the client to the server, to ask for completion options.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["completion/complete"] + params: CompleteRequestParams + + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + + +class CreateTaskResult(OracleModel): + """A response to a task-augmented request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + task: Task + + +class RequestedSchema(OracleModel): + """A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, PrimitiveSchemaDefinition] + required: list[str] | None = None + type: Literal["object"] + + +class ElicitRequestFormParams(OracleModel): + """The parameters for a request to elicit non-sensitive information from the user via a form in the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + message: str + """ + The message to present to the user describing what information is being requested. + """ + mode: Literal["form"] = "form" + """ + The elicitation mode. + """ + requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + """ + A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + + +ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams + + +class GetPromptRequest(OracleModel): + """Used by the client to get a prompt provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/get"] + params: GetPromptRequestParams + + +class GetTaskResult(Result, Task): + """The response to a tasks/get request.""" + + model_config = ConfigDict( + extra="allow", + ) + + +class InitializeRequest(OracleModel): + """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["initialize"] + params: InitializeRequestParams + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + + +JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse + + +class ListPromptsRequest(OracleModel): + """Sent from the client to request a list of prompts and prompt templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/list"] + params: PaginatedRequestParams | None = None + + +class ListPromptsResult(OracleModel): + """The server's response to a prompts/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + prompts: list[Prompt] + + +class ListResourceTemplatesRequest(OracleModel): + """Sent from the client to request a list of resource templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/templates/list"] + params: PaginatedRequestParams | None = None + + +class ListResourceTemplatesResult(OracleModel): + """The server's response to a resources/templates/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + + +class ListResourcesRequest(OracleModel): + """Sent from the client to request a list of resources the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/list"] + params: PaginatedRequestParams | None = None + + +class ListResourcesResult(OracleModel): + """The server's response to a resources/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resources: list[Resource] + + +class ListTasksRequest(OracleModel): + """A request to retrieve a list of tasks.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/list"] + params: PaginatedRequestParams | None = None + + +class ListTasksResult(OracleModel): + """The response to a tasks/list request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tasks: list[Task] + + +class ListToolsRequest(OracleModel): + """Sent from the client to request a list of tools the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/list"] + params: PaginatedRequestParams | None = None + + +class ListToolsResult(OracleModel): + """The server's response to a tools/list request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + tools: list[Tool] + + +class PaginatedRequest(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: PaginatedRequestParams | None = None + + +class ProgressNotification(OracleModel): + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/progress"] + params: ProgressNotificationParams + + +class PromptMessage(OracleModel): + """Describes a message returned as part of a prompt. + + This is similar to `SamplingMessage`, but also supports the embedding of + resources from the MCP server. + """ + + model_config = ConfigDict( + extra="allow", + ) + content: ContentBlock + role: Role + + +class TaskStatusNotification(OracleModel): + """An optional notification from the receiver to the requestor, informing them that a task's status has changed. Receivers are not required to send these notifications.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/tasks/status"] + params: TaskStatusNotificationParams + + +class ToolResultContent(OracleModel): + """The result of a tool use, provided by the user back to the assistant.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool result. Clients SHOULD preserve this field when + including tool results in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + content: list[ContentBlock] + """ + The unstructured result content of the tool use. + + This has the same format as CallToolResult.content and can include text, images, + audio, resource links, and embedded resources. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool use resulted in an error. + + If true, the content typically describes the error that occurred. + Default: false + """ + structured_content: Annotated[dict[str, Any] | None, Field(alias="structuredContent")] = None + """ + An optional structured result object. + + If the tool defined an outputSchema, this SHOULD conform to that schema. + """ + tool_use_id: Annotated[str, Field(alias="toolUseId")] + """ + The ID of the tool use this result corresponds to. + + This MUST match the ID from a previous ToolUseContent. + """ + type: Literal["tool_result"] + + +class CallToolResult(OracleModel): + """The server's response to a tool call.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + content: list[ContentBlock] + """ + A list of content objects that represent the unstructured result of the tool call. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ + structured_content: Annotated[dict[str, Any] | None, Field(alias="structuredContent")] = None + """ + An optional JSON object that represents the structured result of the tool call. + """ + + +ClientNotification: TypeAlias = ( + CancelledNotification + | InitializedNotification + | ProgressNotification + | TaskStatusNotification + | RootsListChangedNotification +) + + +ClientRequest: TypeAlias = ( + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | SetLevelRequest + | CompleteRequest +) + + +class ElicitRequest(OracleModel): + """A request from the server to elicit additional information from the user via the client.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["elicitation/create"] + params: ElicitRequestParams + + +class GetPromptResult(OracleModel): + """The server's response to a prompts/get request from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + description: str | None = None + """ + An optional description for the prompt. + """ + messages: list[PromptMessage] + + +SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | TaskStatusNotification + | LoggingMessageNotification + | ElicitationCompleteNotification +) + + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CompleteResult +) + + +class CreateMessageResult(OracleModel): + """The client's response to a sampling/createMessage request from the server. + The client should inform the user before returning the sampled message, to allow them + to inspect the response (human in the loop) and decide whether to allow the server to see it. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + The reason why sampling stopped, if known. + + Standard values: + - "endTurn": Natural end of the assistant's turn + - "stopSequence": A stop sequence was encountered + - "maxTokens": Maximum token limit was reached + - "toolUse": The model wants to use one or more tools + + This field is an open string to allow for provider-specific stop reasons. + """ + + +class SamplingMessage(OracleModel): + """Describes a message issued to or received from an LLM API.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + role: Role + + +ClientResult: TypeAlias = ( + Result + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CreateMessageResult + | ListRootsResult + | ElicitResult +) + + +class CreateMessageRequestParams(OracleModel): + """Parameters for a `sampling/createMessage` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + The client MAY ignore this request. + + Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if the client + declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. + """ + max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. + """ + messages: list[SamplingMessage] + metadata: dict[str, Any] | None = None + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ + task: TaskMetadata | None = None + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ + temperature: float | None = None + tool_choice: Annotated[ToolChoice | None, Field(alias="toolChoice")] = None + """ + Controls how the model uses tools. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + Default is `{ mode: "auto" }`. + """ + tools: list[Tool] | None = None + """ + Tools that the model may use during generation. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + """ + + +class CreateMessageRequest(OracleModel): + """A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["sampling/createMessage"] + params: CreateMessageRequestParams + + +ServerRequest: TypeAlias = ( + PingRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | CreateMessageRequest + | ListRootsRequest + | ElicitRequest +) + +SPEC_DEFS: tuple[str, ...] = ( + "Annotations", + "AudioContent", + "BaseMetadata", + "BlobResourceContents", + "BooleanSchema", + "CallToolRequest", + "CallToolRequestParams", + "CallToolResult", + "CancelTaskRequest", + "CancelTaskResult", + "CancelledNotification", + "CancelledNotificationParams", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteRequestParams", + "CompleteResult", + "ContentBlock", + "CreateMessageRequest", + "CreateMessageRequestParams", + "CreateMessageResult", + "CreateTaskResult", + "Cursor", + "ElicitRequest", + "ElicitRequestFormParams", + "ElicitRequestParams", + "ElicitRequestURLParams", + "ElicitResult", + "ElicitationCompleteNotification", + "EmbeddedResource", + "EmptyResult", + "EnumSchema", + "Error", + "GetPromptRequest", + "GetPromptRequestParams", + "GetPromptResult", + "GetTaskPayloadRequest", + "GetTaskPayloadResult", + "GetTaskRequest", + "GetTaskResult", + "Icon", + "Icons", + "ImageContent", + "Implementation", + "InitializeRequest", + "InitializeRequestParams", + "InitializeResult", + "InitializedNotification", + "JSONRPCErrorResponse", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "JSONRPCResultResponse", + "LegacyTitledEnumSchema", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListTasksRequest", + "ListTasksResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "LoggingMessageNotificationParams", + "ModelHint", + "ModelPreferences", + "MultiSelectEnumSchema", + "Notification", + "NotificationParams", + "NumberSchema", + "PaginatedRequest", + "PaginatedRequestParams", + "PaginatedResult", + "PingRequest", + "PrimitiveSchemaDefinition", + "ProgressNotification", + "ProgressNotificationParams", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceRequestParams", + "ReadResourceResult", + "RelatedTaskMetadata", + "Request", + "RequestId", + "RequestParams", + "Resource", + "ResourceContents", + "ResourceLink", + "ResourceListChangedNotification", + "ResourceRequestParams", + "ResourceTemplate", + "ResourceTemplateReference", + "ResourceUpdatedNotification", + "ResourceUpdatedNotificationParams", + "Result", + "Role", + "Root", + "RootsListChangedNotification", + "SamplingMessage", + "SamplingMessageContentBlock", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "SetLevelRequestParams", + "SingleSelectEnumSchema", + "StringSchema", + "SubscribeRequest", + "SubscribeRequestParams", + "Task", + "TaskAugmentedRequestParams", + "TaskMetadata", + "TaskStatus", + "TaskStatusNotification", + "TaskStatusNotificationParams", + "TextContent", + "TextResourceContents", + "TitledMultiSelectEnumSchema", + "TitledSingleSelectEnumSchema", + "Tool", + "ToolAnnotations", + "ToolChoice", + "ToolExecution", + "ToolListChangedNotification", + "ToolResultContent", + "ToolUseContent", + "URLElicitationRequiredError", + "UnsubscribeRequest", + "UnsubscribeRequestParams", + "UntitledMultiSelectEnumSchema", + "UntitledSingleSelectEnumSchema", +) diff --git a/tests/spec_oracles/v2026_07_28.py b/tests/spec_oracles/v2026_07_28.py new file mode 100644 index 0000000000..08ce4ec58f --- /dev/null +++ b/tests/spec_oracles/v2026_07_28.py @@ -0,0 +1,3346 @@ +# GENERATED FILE — DO NOT EDIT. +# Source: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/6d441518de8a9d5adbab0b10a76a667a63f90665/schema/draft/schema.json +# Protocol version: 2026-07-28 Generator: datamodel-code-generator 0.57.0 +# Regenerate: uv run --frozen python scripts/update_spec_types.py draft [--sha ] +# pyright: reportIncompatibleVariableOverride=false +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import AnyUrl, Base64Str, ConfigDict, Field +from typing_extensions import TypeAliasType + +from tests.spec_oracles._base import OracleModel + +JSONValue = TypeAliasType("JSONValue", "JSONObject | list[JSONValue] | (str | int | bool)") + + +JSONObject = TypeAliasType("JSONObject", dict[str, "JSONValue"]) + + +class BaseMetadata(OracleModel): + """Base interface for metadata with name (identifier) and title (display name) properties.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class BooleanSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + default: bool | None = None + description: str | None = None + title: str | None = None + type: Literal["boolean"] + + +class Argument(OracleModel): + """The argument's information""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + The name of the argument + """ + value: str + """ + The value of the argument to use for completion matching. + """ + + +class Context(OracleModel): + """Additional, optional context for completions""" + + model_config = ConfigDict( + extra="allow", + ) + arguments: dict[str, str] | None = None + """ + Previously-resolved variables in a URI template or prompt. + """ + + +class Completion(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + has_more: Annotated[bool | None, Field(alias="hasMore")] = None + """ + Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. + """ + total: int | None = None + """ + The total number of completion options available. This can exceed the number of values actually sent in the response. + """ + values: Annotated[list[str], Field(max_length=100)] + """ + An array of completion values. Must not exceed 100 items. + """ + + +Cursor: TypeAlias = str + + +class ElicitRequestURLParams(OracleModel): + """The parameters for a request to elicit information from the user via a URL in the client.""" + + model_config = ConfigDict( + extra="allow", + ) + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ + The ID of the elicitation, which must be unique within the context of the server. + The client MUST treat this ID as an opaque value. + """ + message: str + """ + The message to present to the user explaining why the interaction is needed. + """ + mode: Literal["url"] + """ + The elicitation mode. + """ + url: AnyUrl + """ + The URL that the user should navigate to. + """ + + +class ElicitResult(OracleModel): + """The result returned by the client for an ElicitRequestelicitation/create request.""" + + model_config = ConfigDict( + extra="allow", + ) + action: Literal["accept", "cancel", "decline"] + """ + The user action in response to the elicitation. + - `"accept"`: User submitted the form/confirmed the action + - `"decline"`: User explicitly declined the action + - `"cancel"`: User dismissed without making an explicit choice + """ + content: dict[str, list[str] | str | int | bool] | None = None + """ + The submitted form data, only present when action is `"accept"` and mode was `"form"`. + Contains values matching the requested schema. + Omitted for out-of-band mode responses. + """ + + +class Params(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ + The ID of the elicitation that completed. + """ + + +class ElicitationCompleteNotification(OracleModel): + """An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/elicitation/complete"] + params: Params + + +class Error(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + code: int + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class Icon(OracleModel): + """An optionally-sized icon that can be displayed in a user interface.""" + + model_config = ConfigDict( + extra="allow", + ) + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + Optional MIME type override if the source MIME type is missing or generic. + For example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`. + """ + sizes: list[str] | None = None + """ + Optional array of strings that specify sizes at which the icon can be used. + Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + + If not provided, the client should assume that the icon can be used at any size. + """ + src: AnyUrl + """ + A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a + `data:` URI with Base64-encoded image data. + + Consumers SHOULD take steps to ensure URLs serving icons are from the + same domain as the client/server or a trusted domain. + + Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain + executable JavaScript. + """ + theme: Literal["dark", "light"] | None = None + """ + Optional specifier for the theme this icon is designed for. `"light"` indicates + the icon is designed to be used with a light background, and `"dark"` indicates + the icon is designed to be used with a dark background. + + If not provided, the client should assume the icon can be used with any theme. + """ + + +class Icons(OracleModel): + """Base interface to add `icons` property.""" + + model_config = ConfigDict( + extra="allow", + ) + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + + +class Implementation(OracleModel): + """Describes the MCP implementation.""" + + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + """ + An optional human-readable description of what this implementation does. + + This can be used by clients or servers to provide context about their purpose + and capabilities. For example, a server might describe the types of resources + or tools it provides, while a client might describe its intended use case. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + version: str + """ + The version of this implementation. + """ + website_url: Annotated[AnyUrl | None, Field(alias="websiteUrl")] = None + """ + An optional URL of the website for this implementation. + """ + + +class InternalError(OracleModel): + """A JSON-RPC error indicating that an internal error occurred on the receiver. This error is returned when the receiver encounters an unexpected condition that prevents it from fulfilling the request.""" + + model_config = ConfigDict( + extra="allow", + ) + code: Literal[-32603] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class InvalidParamsError(OracleModel): + """A JSON-RPC error indicating that the method parameters are invalid or malformed. + + In MCP, this error is returned in various contexts when request parameters fail validation: + + - **Tools**: Unknown tool name or invalid tool arguments + - **Prompts**: Unknown prompt name or missing required arguments + - **Pagination**: Invalid or expired cursor values + - **Logging**: Invalid log level + - **Elicitation**: Server requests an elicitation mode not declared in client capabilities + - **Sampling**: Missing tool result or tool results mixed with other content + """ + + model_config = ConfigDict( + extra="allow", + ) + code: Literal[-32602] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class InvalidRequestError(OracleModel): + """A JSON-RPC error indicating that the request is not a valid request object. This error is returned when the message structure does not conform to the JSON-RPC 2.0 specification requirements for a request (e.g., missing required fields like `jsonrpc` or `method`, or using invalid types for these fields).""" + + model_config = ConfigDict( + extra="allow", + ) + code: Literal[-32600] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class JSONRPCNotification(OracleModel): + """A notification which does not expect a response.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class LegacyTitledEnumSchema(OracleModel): + """Use TitledSingleSelectEnumSchema instead. + This interface will be removed in a future version. + """ + + model_config = ConfigDict( + extra="allow", + ) + default: str | None = None + description: str | None = None + enum: list[str] + enum_names: Annotated[list[str] | None, Field(alias="enumNames")] = None + """ + (Legacy) Display names for enum values. + Non-standard according to JSON schema 2020-12. + """ + title: str | None = None + type: Literal["string"] + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +class MetaObject(OracleModel): + """Represents the contents of a `_meta` field, which clients and servers use to attach additional metadata to their interactions. + + Certain key names are reserved by MCP for protocol-level metadata; implementations MUST NOT make assumptions about values at these keys. Additionally, specific schema definitions may reserve particular names for purpose-specific metadata, as declared in those definitions. + + Valid keys have two segments: + + **Prefix:** + - Optional — if specified, MUST be a series of _labels_ separated by dots (`.`), followed by a slash (`/`). + - Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or hyphens (`-`). + - Implementations SHOULD use reverse DNS notation (e.g., `com.example/` rather than `example.com/`). + - Any prefix where the second label is `modelcontextprotocol` or `mcp` is **reserved** for MCP use. For example: `io.modelcontextprotocol/`, `dev.mcp/`, `org.modelcontextprotocol.api/`, and `com.mcp.tools/` are all reserved. However, `com.example.mcp/` is NOT reserved, as the second label is `example`. + + **Name:** + - Unless empty, MUST start and end with an alphanumeric character (`[a-z0-9A-Z]`). + - Interior characters may be alphanumeric, hyphens (`-`), underscores (`_`), or dots (`.`). + """ + + model_config = ConfigDict( + extra="allow", + ) + + +class MethodNotFoundError(OracleModel): + """A JSON-RPC error indicating that the requested method does not exist or is not available. + + In MCP, a server returns this error when a client invokes a method the server does not implement — either a genuinely unknown method, or one gated behind a server capability the server did not advertise (e.g., calling `prompts/list` when the `prompts` capability was not advertised). + + A request that requires a client capability the client did not declare is signalled instead by MissingRequiredClientCapabilityError (`-32003`). + """ + + model_config = ConfigDict( + extra="allow", + ) + code: Literal[-32601] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +class ModelHint(OracleModel): + """Hints to use for model selection. + + Keys not declared here are currently left unspecified by the spec and are up + to the client to interpret. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: str | None = None + """ + A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` + """ + + +class ModelPreferences(OracleModel): + """The server's preferences for model selection, requested of the client during sampling. + + Because LLMs can vary along multiple dimensions, choosing the "best" model is + rarely straightforward. Different models excel in different areas—some are + faster but less capable, others are more capable but more expensive, and so + on. This interface allows servers to express their priorities across multiple + dimensions to help clients make an appropriate selection for their use case. + + These preferences are always advisory. The client MAY ignore them. It is also + up to the client to decide how to interpret these preferences and how to + balance them against other considerations. + """ + + model_config = ConfigDict( + extra="allow", + ) + cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + """ + hints: list[ModelHint] | None = None + """ + Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. + """ + intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + """ + speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + """ + + +class Notification(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: dict[str, Any] | None = None + + +class NotificationParams(OracleModel): + """Common params for any notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + + +class NumberSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + default: float | None = None + description: str | None = None + maximum: float | None = None + minimum: float | None = None + title: str | None = None + type: Literal["integer", "number"] + + +class PaginatedResult(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +class ParseError(OracleModel): + """A JSON-RPC error indicating that invalid JSON was received by the server. This error is returned when the server cannot parse the JSON text of a message.""" + + model_config = ConfigDict( + extra="allow", + ) + code: Literal[-32700] + """ + The error type that occurred. + """ + data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ + + +ProgressToken: TypeAlias = str | int + + +class PromptArgument(OracleModel): + """Describes an argument that a prompt can accept.""" + + model_config = ConfigDict( + extra="allow", + ) + description: str | None = None + """ + A human-readable description of the argument. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + required: bool | None = None + """ + Whether this argument must be provided. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class PromptListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/prompts/list_changed"] + params: NotificationParams | None = None + + +class PromptReference(OracleModel): + """Identifies a prompt.""" + + model_config = ConfigDict( + extra="allow", + ) + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["ref/prompt"] + + +class Request(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + method: str + params: dict[str, Any] | None = None + + +RequestId: TypeAlias = str | int + + +class ResourceContents(OracleModel): + """The contents of a specific resource or sub-resource.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/list_changed"] + params: NotificationParams | None = None + + +class ResourceTemplateReference(OracleModel): + """A reference to a resource or resource template definition.""" + + model_config = ConfigDict( + extra="allow", + ) + type: Literal["ref/resource"] + uri: str + """ + The URI or URI template of the resource. + """ + + +class ResourceUpdatedNotificationParams(OracleModel): + """Parameters for a `notifications/resources/updated` notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + uri: AnyUrl + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. + """ + + +class Result(OracleModel): + """Common result fields.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +ResultType: TypeAlias = str + + +Role: TypeAlias = Literal["assistant", "user"] + + +class Root(OracleModel): + """Represents a root directory or file that the server can operate on.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ + uri: AnyUrl + """ + The URI identifying the root. This *must* start with `file://` for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ + + +class Prompts(OracleModel): + """Present if the server offers any prompt templates.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the prompt list. + """ + + +class Resources(OracleModel): + """Present if the server offers any resources to read.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the resource list. + """ + subscribe: bool | None = None + """ + Whether this server supports subscribing to resource updates. + """ + + +class Tools(OracleModel): + """Present if the server offers any tools to call.""" + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the tool list. + """ + + +class StringSchema(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + default: str | None = None + description: str | None = None + format: Literal["date", "date-time", "email", "uri"] | None = None + max_length: Annotated[int | None, Field(alias="maxLength")] = None + min_length: Annotated[int | None, Field(alias="minLength")] = None + title: str | None = None + type: Literal["string"] + + +class SubscriptionFilter(OracleModel): + """The set of notification types a client may opt in to on a + SubscriptionsListenRequestsubscriptions/listen request. + + Each notification type is **opt-in**; the server **MUST NOT** send + notification types the client has not explicitly requested here. + """ + + model_config = ConfigDict( + extra="allow", + ) + prompts_list_changed: Annotated[bool | None, Field(alias="promptsListChanged")] = None + """ + If true, receive PromptListChangedNotificationnotifications/prompts/list_changed. + """ + resource_subscriptions: Annotated[list[str] | None, Field(alias="resourceSubscriptions")] = None + """ + Subscribe to ResourceUpdatedNotificationnotifications/resources/updated for these resource URIs. + Replaces the former `resources/subscribe` RPC. + """ + resources_list_changed: Annotated[bool | None, Field(alias="resourcesListChanged")] = None + """ + If true, receive ResourceListChangedNotificationnotifications/resources/list_changed. + """ + tools_list_changed: Annotated[bool | None, Field(alias="toolsListChanged")] = None + """ + If true, receive ToolListChangedNotificationnotifications/tools/list_changed. + """ + + +class SubscriptionsAcknowledgedNotificationParams(OracleModel): + """Parameters for a SubscriptionsAcknowledgedNotificationnotifications/subscriptions/acknowledged notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + notifications: SubscriptionFilter + """ + The subset of requested notification types the server agreed to honor. + Only includes notification types the server actually supports; if the + client requested an unsupported type (e.g., `promptsListChanged` when + the server has no prompts), it is omitted from this set. + """ + + +class TextResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class AnyOfItem(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + const: str + """ + The constant enum value. + """ + title: str + """ + Display title for this option. + """ + + +class Items(OracleModel): + """Schema for array items with enum options and display labels.""" + + model_config = ConfigDict( + extra="allow", + ) + any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] + """ + Array of enum options with values and display labels. + """ + + +class TitledMultiSelectEnumSchema(OracleModel): + """Schema for multiple-selection enumeration with display titles for each option.""" + + model_config = ConfigDict( + extra="allow", + ) + default: list[str] | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + items: Items + """ + Schema for array items with enum options and display labels. + """ + max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ + min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["array"] + + +class OneOfItem(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + const: str + """ + The enum value. + """ + title: str + """ + Display label for this option. + """ + + +class TitledSingleSelectEnumSchema(OracleModel): + """Schema for single-selection enumeration with display titles for each option.""" + + model_config = ConfigDict( + extra="allow", + ) + default: str | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + one_of: Annotated[list[OneOfItem], Field(alias="oneOf")] + """ + Array of enum options with values and display labels. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["string"] + + +class InputSchema(OracleModel): + """A JSON Schema object defining the expected parameters for the tool. + + Tool arguments are always JSON objects, so `type: "object"` is required at the root. + Beyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including + composition keywords (`oneOf`, `anyOf`, `allOf`, `not`), conditional keywords + (`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other + standard validation or annotation keywords. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ + + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + type: Literal["object"] + + +class OutputSchema(OracleModel): + """An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ + + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + + +class ToolAnnotations(OracleModel): + """Additional properties describing a Tool to clients. + + NOTE: all properties in `ToolAnnotations` are **hints**. + They are not guaranteed to provide a faithful description of + tool behavior (including descriptive properties like `title`). + + Clients should never make tool use decisions based on `ToolAnnotations` + received from untrusted servers. + """ + + model_config = ConfigDict( + extra="allow", + ) + destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None + """ + If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: true + """ + idempotent_hint: Annotated[bool | None, Field(alias="idempotentHint")] = None + """ + If true, calling the tool repeatedly with the same arguments + will have no additional effect on its environment. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: false + """ + open_world_hint: Annotated[bool | None, Field(alias="openWorldHint")] = None + """ + If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + + Default: true + """ + read_only_hint: Annotated[bool | None, Field(alias="readOnlyHint")] = None + """ + If true, the tool does not modify its environment. + + Default: false + """ + title: str | None = None + """ + A human-readable title for the tool. + """ + + +class ToolChoice(OracleModel): + """Controls tool selection behavior for sampling requests.""" + + model_config = ConfigDict( + extra="allow", + ) + mode: Literal["auto", "none", "required"] | None = None + """ + Controls the tool use ability of the model: + - `"auto"`: Model decides whether to use tools (default) + - `"required"`: Model MUST use at least one tool before completing + - `"none"`: Model MUST NOT use any tools + """ + + +class ToolListChangedNotification(OracleModel): + """An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/tools/list_changed"] + params: NotificationParams | None = None + + +class ToolUseContent(OracleModel): + """A request from the assistant to call a tool.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool use. Clients SHOULD preserve this field when + including tool uses in subsequent sampling requests to enable caching optimizations. + """ + id: str + """ + A unique identifier for this tool use. + + This ID is used to match tool results to their corresponding tool uses. + """ + input: dict[str, Any] + """ + The arguments to pass to the tool, conforming to the tool's input schema. + """ + name: str + """ + The name of the tool to call. + """ + type: Literal["tool_use"] + + +class Data1(OracleModel): + """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.).""" + + model_config = ConfigDict( + extra="allow", + ) + requested: str + """ + The protocol version that was requested by the client. + """ + supported: list[str] + """ + Protocol versions the server supports. The client should choose a + mutually supported version from this list and retry. + """ + + +class Error2(Error): + model_config = ConfigDict( + extra="allow", + ) + code: Literal[-32004] + """ + The error type that occurred. + """ + data: Data1 + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + + +class UnsupportedProtocolVersionError(OracleModel): + """Returned when the request's protocol version is unknown to the server or + unsupported (e.g., a known experimental or draft version the server has + chosen not to implement). For HTTP, the response status code MUST be + `400 Bad Request`. + """ + + model_config = ConfigDict( + extra="allow", + ) + error: Error2 + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class Items1(OracleModel): + """Schema for the array items.""" + + model_config = ConfigDict( + extra="allow", + ) + enum: list[str] + """ + Array of enum values to choose from. + """ + type: Literal["string"] + + +class UntitledMultiSelectEnumSchema(OracleModel): + """Schema for multiple-selection enumeration without display titles for options.""" + + model_config = ConfigDict( + extra="allow", + ) + default: list[str] | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + items: Items1 + """ + Schema for the array items. + """ + max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ + min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["array"] + + +class UntitledSingleSelectEnumSchema(OracleModel): + """Schema for single-selection enumeration without display titles for options.""" + + model_config = ConfigDict( + extra="allow", + ) + default: str | None = None + """ + Optional default value. + """ + description: str | None = None + """ + Optional description for the enum field. + """ + enum: list[str] + """ + Array of enum values to choose from. + """ + title: str | None = None + """ + Optional title for the enum field. + """ + type: Literal["string"] + + +class Annotations(OracleModel): + """Optional annotations for the client. The client can use annotations to inform how objects are used or displayed""" + + model_config = ConfigDict( + extra="allow", + ) + audience: list[Role] | None = None + """ + Describes who the intended audience of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). + """ + last_modified: Annotated[str | None, Field(alias="lastModified")] = None + """ + The moment the resource was last modified, as an ISO 8601 formatted string. + + Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + + Examples: last activity timestamp in an open file, timestamp when the resource + was attached, etc. + """ + priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None + """ + Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + """ + + +class AudioContent(OracleModel): + """Audio provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: Base64Str + """ + The base64-encoded audio data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the audio. Different providers may support different audio types. + """ + type: Literal["audio"] + + +class BlobResourceContents(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + blob: Base64Str + """ + A base64-encoded string representing the binary data of the item. + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class CacheableResult(OracleModel): + """A result that supports a time-to-live (TTL) hint for client-side caching.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class CancelledNotificationParams(OracleModel): + """Parameters for a `notifications/cancelled` notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ + request_id: Annotated[RequestId | None, Field(alias="requestId")] = None + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + """ + + +ClientResult: TypeAlias = Result + + +class CompleteResult(OracleModel): + """The result returned by the server for a CompleteRequestcompletion/complete request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + completion: Completion + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +class CompleteResultResponse(OracleModel): + """A successful response from the server for a CompleteRequestcompletion/complete request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: CompleteResult + + +class EmbeddedResource(OracleModel): + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +EmptyResult: TypeAlias = Result + + +EnumSchema: TypeAlias = ( + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +class ImageContent(OracleModel): + """An image provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: Base64Str + """ + The base64-encoded image data. + """ + mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ + type: Literal["image"] + + +class JSONRPCErrorResponse(OracleModel): + """A response to a request that indicates an error occurred.""" + + model_config = ConfigDict( + extra="allow", + ) + error: Error + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class JSONRPCRequest(OracleModel): + """A request that expects a response.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class JSONRPCResultResponse(OracleModel): + """A successful (non-error) response to a request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class ListRootsResult(OracleModel): + """The result returned by the client for a ListRootsRequestroots/list request. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ + + model_config = ConfigDict( + extra="allow", + ) + roots: list[Root] + + +class LoggingMessageNotificationParams(OracleModel): + """Parameters for a `notifications/message` notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ + level: LoggingLevel + """ + The severity of this log message. + """ + logger: str | None = None + """ + An optional name of the logger issuing this message. + """ + + +MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema + + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +class ProgressNotificationParams(OracleModel): + """Parameters for a ProgressNotificationnotifications/progress notification.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + message: str | None = None + """ + An optional message describing the current progress. + """ + progress: float + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """ + The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. + """ + total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ + + +class Prompt(OracleModel): + """A prompt or prompt template that the server offers.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ + description: str | None = None + """ + An optional description of what this prompt provides + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class ReadResourceResult(OracleModel): + """The result returned by the server for a ReadResourceRequestresources/read request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + contents: list[TextResourceContents | BlobResourceContents] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class Resource(OracleModel): + """A known resource that the server is capable of reading.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceLink(OracleModel): + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of ListResourcesRequestresources/list requests. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + size: int | None = None + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + type: Literal["resource_link"] + uri: AnyUrl + """ + The URI of this resource. + """ + + +class ResourceTemplate(OracleModel): + """A template description for resources available on the server.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + uri_template: Annotated[str, Field(alias="uriTemplate")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +class ResourceUpdatedNotification(OracleModel): + """A notification from the server to the client, informing it that a resource has changed and may need to be read again. This is only sent for resources the client opted in to via the `resourceSubscriptions` field of a SubscriptionsListenRequestsubscriptions/listen request.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/updated"] + params: ResourceUpdatedNotificationParams + + +SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema + + +class SubscriptionsAcknowledgedNotification(OracleModel): + """Sent by the server as the first message on a + SubscriptionsListenRequestsubscriptions/listen stream to acknowledge + that the subscription has been established and to report which notification + types it agreed to honor. + """ + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/subscriptions/acknowledged"] + params: SubscriptionsAcknowledgedNotificationParams + + +class TextContent(OracleModel): + """Text provided to or from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + text: str + """ + The text content of the message. + """ + type: Literal["text"] + + +class Tool(OracleModel): + """Definition for a tool the client can call.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: ToolAnnotations | None = None + """ + Optional additional tool information. + + Display name precedence order is: `title`, `annotations.title`, then `name`. + """ + description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model. + """ + icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + + Tool arguments are always JSON objects, so `type: "object"` is required at the root. + Beyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including + composition keywords (`oneOf`, `anyOf`, `allOf`, `not`), conditional keywords + (`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other + standard validation or annotation keywords. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ + name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). + """ + output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + """ + An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ + title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ + + +class CancelledNotification(OracleModel): + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + """ + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/cancelled"] + params: CancelledNotificationParams + + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + + +class RequestedSchema(OracleModel): + """A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, PrimitiveSchemaDefinition] + required: list[str] | None = None + type: Literal["object"] + + +class ElicitRequestFormParams(OracleModel): + """The parameters for a request to elicit non-sensitive information from the user via a form in the client.""" + + model_config = ConfigDict( + extra="allow", + ) + message: str + """ + The message to present to the user describing what information is being requested. + """ + mode: Literal["form"] = "form" + """ + The elicitation mode. + """ + requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + """ + A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ + + +ElicitRequestParams: TypeAlias = ElicitRequestFormParams | ElicitRequestURLParams + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + + +JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse + + +class ListPromptsResult(OracleModel): + """The result returned by the server for a ListPromptsRequestprompts/list request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + prompts: list[Prompt] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class ListPromptsResultResponse(OracleModel): + """A successful response from the server for a ListPromptsRequestprompts/list request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: ListPromptsResult + + +class ListResourceTemplatesResult(OracleModel): + """The result returned by the server for a ListResourceTemplatesRequestresources/templates/list request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class ListResourceTemplatesResultResponse(OracleModel): + """A successful response from the server for a ListResourceTemplatesRequestresources/templates/list request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourceTemplatesResult + + +class ListResourcesResult(OracleModel): + """The result returned by the server for a ListResourcesRequestresources/list request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + resources: list[Resource] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class ListResourcesResultResponse(OracleModel): + """A successful response from the server for a ListResourcesRequestresources/list request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourcesResult + + +class ListToolsResult(OracleModel): + """The result returned by the server for a ListToolsRequesttools/list request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + tools: list[Tool] + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class ListToolsResultResponse(OracleModel): + """A successful response from the server for a ListToolsRequesttools/list request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: ListToolsResult + + +class LoggingMessageNotification(OracleModel): + """JSONRPCNotification of a log message passed from server to client. The client opts in by setting `"io.modelcontextprotocol/logLevel"` in a request's `_meta`.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/message"] + params: LoggingMessageNotificationParams + + +class ProgressNotification(OracleModel): + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + + model_config = ConfigDict( + extra="allow", + ) + jsonrpc: Literal["2.0"] + method: Literal["notifications/progress"] + params: ProgressNotificationParams + + +class PromptMessage(OracleModel): + """Describes a message returned as part of a prompt. + + This is similar to SamplingMessage, but also supports the embedding of + resources from the MCP server. + """ + + model_config = ConfigDict( + extra="allow", + ) + content: ContentBlock + role: Role + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | SubscriptionsAcknowledgedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification + | ElicitationCompleteNotification +) + + +class ToolResultContent(OracleModel): + """The result of a tool use, provided by the user back to the assistant.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool result. Clients SHOULD preserve this field when + including tool results in subsequent sampling requests to enable caching optimizations. + """ + content: list[ContentBlock] + """ + The unstructured result content of the tool use. + + This has the same format as CallToolResult.content and can include text, images, + audio, resource links, and embedded resources. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool use resulted in an error. + + If true, the content typically describes the error that occurred. + Default: false + """ + structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None + """ + An optional structured result value. + + This can be any JSON value (object, array, string, number, boolean, or null). + If the tool defined an Tool.outputSchema, this SHOULD conform to that schema. + """ + tool_use_id: Annotated[str, Field(alias="toolUseId")] + """ + The ID of the tool use this result corresponds to. + + This MUST match the ID from a previous ToolUseContent. + """ + type: Literal["tool_result"] + + +class CallToolResult(OracleModel): + """The result returned by the server for a CallToolRequesttools/call request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: list[ContentBlock] + """ + A list of content objects that represent the unstructured result of the tool call. + """ + is_error: Annotated[bool | None, Field(alias="isError")] = None + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None + """ + An optional JSON value that represents the structured result of the tool call. + + This can be any JSON value (object, array, string, number, boolean, or null) + that conforms to the tool's outputSchema if one is defined. + """ + + +ClientNotification: TypeAlias = CancelledNotification | ProgressNotification + + +class ElicitRequest(OracleModel): + """A request from the server to elicit additional information from the user via the client.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["elicitation/create"] + params: ElicitRequestParams + + +class GetPromptResult(OracleModel): + """The result returned by the server for a GetPromptRequestprompts/get request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + description: str | None = None + """ + An optional description for the prompt. + """ + messages: list[PromptMessage] + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent + + +class CreateMessageResult(OracleModel): + """The result returned by the client for a CreateMessageRequestsampling/createMessage request. + The client should inform the user before returning the sampled message, to allow them + to inspect the response (human in the loop) and decide whether to allow the server to see it. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + The reason why sampling stopped, if known. + + Standard values: + - `"endTurn"`: Natural end of the assistant's turn + - `"stopSequence"`: A stop sequence was encountered + - `"maxTokens"`: Maximum token limit was reached + - `"toolUse"`: The model wants to use one or more tools + + This field is an open string to allow for provider-specific stop reasons. + """ + + +InputResponse: TypeAlias = CreateMessageResult | ListRootsResult | ElicitResult + + +InputResponses: TypeAlias = dict[str, InputResponse] +"""A map of client responses to server-initiated requests. +Keys correspond to the keys in the InputRequests map; +values are the client's result for each request.""" + + +class SamplingMessage(OracleModel): + """Describes a message issued to or received from an LLM API.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + role: Role + + +class CallToolRequest(OracleModel): + """Used by the client to invoke a tool provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/call"] + params: CallToolRequestParams + + +class CallToolRequestParams(OracleModel): + """Parameters for a `tools/call` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + arguments: dict[str, Any] | None = None + """ + Arguments to use for the tool call. + """ + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + name: str + """ + The name of the tool. + """ + request_state: Annotated[str | None, Field(alias="requestState")] = None + + +class CallToolResultResponse(OracleModel): + """A successful response from the server for a CallToolRequesttools/call request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | CallToolResult + + +class Elicitation(OracleModel): + """Present if the client supports elicitation from the server.""" + + model_config = ConfigDict( + extra="allow", + ) + form: JSONObject | None = None + url: JSONObject | None = None + + +class Sampling(OracleModel): + """Present if the client supports sampling from an LLM.""" + + model_config = ConfigDict( + extra="allow", + ) + context: JSONObject | None = None + """ + Whether the client supports context inclusion via `includeContext` parameter. + If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). + """ + tools: JSONObject | None = None + """ + Whether the client supports tool use via `tools` and `toolChoice` parameters. + """ + + +class ClientCapabilities(OracleModel): + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + elicitation: Elicitation | None = None + """ + Present if the client supports elicitation from the server. + """ + experimental: dict[str, JSONObject] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ + extensions: dict[str, JSONObject] | None = None + """ + Optional MCP extensions that the client supports. Keys are extension identifiers + (e.g., "io.modelcontextprotocol/oauth-client-credentials"), and values are + per-extension settings objects. An empty object indicates support with no settings. + """ + roots: dict[str, Any] | None = None + """ + Present if the client supports listing roots. + """ + sampling: Sampling | None = None + """ + Present if the client supports sampling from an LLM. + """ + + +class CompleteRequest(OracleModel): + """A request from the client to the server, to ask for completion options.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["completion/complete"] + params: CompleteRequestParams + + +class CompleteRequestParams(OracleModel): + """Parameters for a `completion/complete` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + argument: Argument + """ + The argument's information + """ + context: Context | None = None + """ + Additional, optional context for completions + """ + ref: PromptReference | ResourceTemplateReference + + +class CreateMessageRequest(OracleModel): + """A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.""" + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["sampling/createMessage"] + params: CreateMessageRequestParams + + +class CreateMessageRequestParams(OracleModel): + """Parameters for a `sampling/createMessage` request.""" + + model_config = ConfigDict( + extra="allow", + ) + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + The client MAY ignore this request. + + Default is `"none"`. The values `"thisServer"` and `"allServers"` are deprecated (SEP-2596): servers SHOULD + omit this field or use `"none"`, and SHOULD only use the deprecated values if the client declares + ClientCapabilities.sampling.context. + """ + max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. + """ + messages: list[SamplingMessage] + metadata: JSONObject | None = None + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ + temperature: float | None = None + tool_choice: Annotated[ToolChoice | None, Field(alias="toolChoice")] = None + """ + Controls how the model uses tools. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + Default is `{ mode: "auto" }`. + """ + tools: list[Tool] | None = None + """ + Tools that the model may use during generation. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + """ + + +class DiscoverRequest(OracleModel): + """A request from the client asking the server to advertise its supported + protocol versions, capabilities, and other metadata. Servers **MUST** + implement `server/discover`. Clients **MAY** call it but are not required + to — version negotiation can also happen inline via per-request `_meta`. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["server/discover"] + params: RequestParams + + +class DiscoverResult(OracleModel): + """The result returned by the server for a DiscoverRequestserver/discover request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ + capabilities: ServerCapabilities + """ + The capabilities of the server. + """ + instructions: str | None = None + """ + Natural-language guidance describing the server and its features. + + This can be used by clients to improve an LLM's understanding of + available tools (e.g., by including it in a system prompt). It should + focus on information that helps the model use the server effectively + and should not duplicate information already in tool descriptions. + """ + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + """ + Information about the server software implementation. + """ + supported_versions: Annotated[list[str], Field(alias="supportedVersions")] + """ + MCP Protocol Versions this server supports. The client should choose a + version from this list for use in subsequent requests. + """ + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ + + +class DiscoverResultResponse(OracleModel): + """A successful response from the server for a DiscoverRequestserver/discover request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: DiscoverResult + + +class GetPromptRequest(OracleModel): + """Used by the client to get a prompt provided by the server.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/get"] + params: GetPromptRequestParams + + +class GetPromptRequestParams(OracleModel): + """Parameters for a `prompts/get` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + name: str + """ + The name of the prompt or prompt template. + """ + request_state: Annotated[str | None, Field(alias="requestState")] = None + + +class GetPromptResultResponse(OracleModel): + """A successful response from the server for a GetPromptRequestprompts/get request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | GetPromptResult + + +class InputRequiredResult(OracleModel): + """An InputRequiredResult sent by the server to indicate that additional input is needed + before the request can be completed. + + At least one of `inputRequests` or `requestState` MUST be present. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + input_requests: Annotated[InputRequests | None, Field(alias="inputRequests")] = None + request_state: Annotated[str | None, Field(alias="requestState")] = None + result_type: Annotated[str, Field(alias="resultType")] + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ + + +class InputResponseRequestParams(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + request_state: Annotated[str | None, Field(alias="requestState")] = None + + +class ListPromptsRequest(OracleModel): + """Sent from the client to request a list of prompts and prompt templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/list"] + params: PaginatedRequestParams + + +class ListResourceTemplatesRequest(OracleModel): + """Sent from the client to request a list of resource templates the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/templates/list"] + params: PaginatedRequestParams + + +class ListResourcesRequest(OracleModel): + """Sent from the client to request a list of resources the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/list"] + params: PaginatedRequestParams + + +class ListRootsRequest(OracleModel): + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ + + model_config = ConfigDict( + extra="allow", + ) + method: Literal["roots/list"] + params: RequestParams | None = None + + +class ListToolsRequest(OracleModel): + """Sent from the client to request a list of tools the server has.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/list"] + params: PaginatedRequestParams + + +class Data(OracleModel): + """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.).""" + + model_config = ConfigDict( + extra="allow", + ) + required_capabilities: Annotated[ClientCapabilities, Field(alias="requiredCapabilities")] + """ + The capabilities the server requires from the client to process this request. + """ + + +class Error1(Error): + model_config = ConfigDict( + extra="allow", + ) + code: Literal[-32003] + """ + The error type that occurred. + """ + data: Data + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). + """ + + +class MissingRequiredClientCapabilityError(OracleModel): + """Returned when processing a request requires a capability the client did not + declare in `clientCapabilities`. For HTTP, the response status code MUST be + `400 Bad Request`. + """ + + model_config = ConfigDict( + extra="allow", + ) + error: Error1 + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class PaginatedRequest(OracleModel): + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: PaginatedRequestParams + + +class PaginatedRequestParams(OracleModel): + """Common params for paginated requests.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class ReadResourceRequest(OracleModel): + """Sent from the client to the server, to read a specific resource URI.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/read"] + params: ReadResourceRequestParams + + +class ReadResourceRequestParams(OracleModel): + """Parameters for a `resources/read` request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + request_state: Annotated[str | None, Field(alias="requestState")] = None + uri: AnyUrl + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class ReadResourceResultResponse(OracleModel): + """A successful response from the server for a ReadResourceRequestresources/read request.""" + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | ReadResourceResult + + +class RequestMetaObject(OracleModel): + """Extends MetaObject with additional request-specific fields. All key naming rules from `MetaObject` apply.""" + + model_config = ConfigDict( + extra="allow", + ) + io_modelcontextprotocol_client_capabilities: Annotated[ + ClientCapabilities, Field(alias="io.modelcontextprotocol/clientCapabilities") + ] + """ + The client's capabilities for this specific request. Required. + + Capabilities are declared per-request rather than once at initialization; + an empty object means the client supports no optional capabilities. + Servers MUST NOT infer capabilities from prior requests. + """ + io_modelcontextprotocol_client_info: Annotated[Implementation, Field(alias="io.modelcontextprotocol/clientInfo")] + """ + Identifies the client software making the request. Required. + + The Implementation schema requires `name` and `version`; other + fields are optional. + """ + io_modelcontextprotocol_log_level: Annotated[ + LoggingLevel | None, Field(alias="io.modelcontextprotocol/logLevel") + ] = None + """ + The desired log level for this request. Optional. + + If absent, the server MUST NOT send any LoggingMessageNotificationnotifications/message + notifications for this request. The client opts in to log messages by + explicitly setting a level. Replaces the former `logging/setLevel` RPC. + """ + io_modelcontextprotocol_protocol_version: Annotated[str, Field(alias="io.modelcontextprotocol/protocolVersion")] + """ + The MCP Protocol Version being used for this request. Required. + + For the HTTP transport, this value MUST match the `MCP-Protocol-Version` + header; otherwise the server MUST return a `400 Bad Request`. If the + server does not support the requested version, it MUST return an + UnsupportedProtocolVersionError. + """ + progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by ProgressNotificationnotifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. + """ + + +class RequestParams(OracleModel): + """Common params for any request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + + +class ResourceRequestParams(OracleModel): + """Common params for resource-related requests.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + uri: AnyUrl + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ + + +class ServerCapabilities(OracleModel): + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.""" + + model_config = ConfigDict( + extra="allow", + ) + completions: JSONObject | None = None + """ + Present if the server supports argument autocompletion suggestions. + """ + experimental: dict[str, JSONObject] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ + extensions: dict[str, JSONObject] | None = None + """ + Optional MCP extensions that the server supports. Keys are extension identifiers + (e.g., "io.modelcontextprotocol/tasks"), and values are per-extension settings + objects. An empty object indicates support with no settings. + """ + logging: JSONObject | None = None + """ + Present if the server supports sending log messages to the client. + """ + prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ + resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ + tools: Tools | None = None + """ + Present if the server offers any tools to call. + """ + + +class SubscriptionsListenRequest(OracleModel): + """Sent from the client to open a long-lived channel for receiving notifications + outside the context of a specific request. Replaces the previous HTTP GET + endpoint and ensures consistent behavior between HTTP and STDIO. + """ + + model_config = ConfigDict( + extra="allow", + ) + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["subscriptions/listen"] + params: SubscriptionsListenRequestParams + + +class SubscriptionsListenRequestParams(OracleModel): + """Parameters for a SubscriptionsListenRequestsubscriptions/listen request.""" + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + notifications: SubscriptionFilter + """ + The notifications the client opts in to on this stream. The server + **MUST NOT** send notification types the client has not explicitly + requested. + """ + + +ServerResult: TypeAlias = ( + Result + | InputRequiredResult + | DiscoverResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) + + +InputRequest: TypeAlias = CreateMessageRequest | ListRootsRequest | ElicitRequest + + +ClientRequest: TypeAlias = ( + DiscoverRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscriptionsListenRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | CompleteRequest +) + + +InputRequests: TypeAlias = dict[str, InputRequest] +"""A map of server-initiated requests that the client must fulfill. +Keys are server-assigned identifiers; values are the request objects.""" + + +JSONArray: TypeAlias = list["JSONValue"] + + +CallToolRequest.model_rebuild() +CallToolRequestParams.model_rebuild() +CallToolResultResponse.model_rebuild() +Elicitation.model_rebuild() +Sampling.model_rebuild() +ClientCapabilities.model_rebuild() +CompleteRequest.model_rebuild() +CompleteRequestParams.model_rebuild() +CreateMessageRequest.model_rebuild() +CreateMessageRequestParams.model_rebuild() +DiscoverRequest.model_rebuild() +DiscoverResult.model_rebuild() +GetPromptRequest.model_rebuild() +GetPromptRequestParams.model_rebuild() +GetPromptResultResponse.model_rebuild() +InputRequiredResult.model_rebuild() +InputResponseRequestParams.model_rebuild() +ListPromptsRequest.model_rebuild() +ListResourceTemplatesRequest.model_rebuild() +ListResourcesRequest.model_rebuild() +ListRootsRequest.model_rebuild() +ListToolsRequest.model_rebuild() +PaginatedRequest.model_rebuild() +PaginatedRequestParams.model_rebuild() +ReadResourceRequest.model_rebuild() +ReadResourceRequestParams.model_rebuild() +ServerCapabilities.model_rebuild() +SubscriptionsListenRequest.model_rebuild() + +SPEC_DEFS: tuple[str, ...] = ( + "Annotations", + "AudioContent", + "BaseMetadata", + "BlobResourceContents", + "BooleanSchema", + "CacheableResult", + "CallToolRequest", + "CallToolRequestParams", + "CallToolResult", + "CallToolResultResponse", + "CancelledNotification", + "CancelledNotificationParams", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteRequestParams", + "CompleteResult", + "CompleteResultResponse", + "ContentBlock", + "CreateMessageRequest", + "CreateMessageRequestParams", + "CreateMessageResult", + "Cursor", + "DiscoverRequest", + "DiscoverResult", + "DiscoverResultResponse", + "ElicitRequest", + "ElicitRequestFormParams", + "ElicitRequestParams", + "ElicitRequestURLParams", + "ElicitResult", + "ElicitationCompleteNotification", + "EmbeddedResource", + "EmptyResult", + "EnumSchema", + "Error", + "GetPromptRequest", + "GetPromptRequestParams", + "GetPromptResult", + "GetPromptResultResponse", + "Icon", + "Icons", + "ImageContent", + "Implementation", + "InputRequest", + "InputRequests", + "InputRequiredResult", + "InputResponse", + "InputResponseRequestParams", + "InputResponses", + "InternalError", + "InvalidParamsError", + "InvalidRequestError", + "JSONArray", + "JSONObject", + "JSONRPCErrorResponse", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "JSONRPCResultResponse", + "JSONValue", + "LegacyTitledEnumSchema", + "ListPromptsRequest", + "ListPromptsResult", + "ListPromptsResultResponse", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourceTemplatesResultResponse", + "ListResourcesRequest", + "ListResourcesResult", + "ListResourcesResultResponse", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "ListToolsResultResponse", + "LoggingLevel", + "LoggingMessageNotification", + "LoggingMessageNotificationParams", + "MetaObject", + "MethodNotFoundError", + "MissingRequiredClientCapabilityError", + "ModelHint", + "ModelPreferences", + "MultiSelectEnumSchema", + "Notification", + "NotificationParams", + "NumberSchema", + "PaginatedRequest", + "PaginatedRequestParams", + "PaginatedResult", + "ParseError", + "PrimitiveSchemaDefinition", + "ProgressNotification", + "ProgressNotificationParams", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "ReadResourceRequest", + "ReadResourceRequestParams", + "ReadResourceResult", + "ReadResourceResultResponse", + "Request", + "RequestId", + "RequestMetaObject", + "RequestParams", + "Resource", + "ResourceContents", + "ResourceLink", + "ResourceListChangedNotification", + "ResourceRequestParams", + "ResourceTemplate", + "ResourceTemplateReference", + "ResourceUpdatedNotification", + "ResourceUpdatedNotificationParams", + "Result", + "ResultType", + "Role", + "Root", + "SamplingMessage", + "SamplingMessageContentBlock", + "ServerCapabilities", + "ServerNotification", + "ServerResult", + "SingleSelectEnumSchema", + "StringSchema", + "SubscriptionFilter", + "SubscriptionsAcknowledgedNotification", + "SubscriptionsAcknowledgedNotificationParams", + "SubscriptionsListenRequest", + "SubscriptionsListenRequestParams", + "TextContent", + "TextResourceContents", + "TitledMultiSelectEnumSchema", + "TitledSingleSelectEnumSchema", + "Tool", + "ToolAnnotations", + "ToolChoice", + "ToolListChangedNotification", + "ToolResultContent", + "ToolUseContent", + "UnsupportedProtocolVersionError", + "UntitledMultiSelectEnumSchema", + "UntitledSingleSelectEnumSchema", +) diff --git a/tests/types/__init__.py b/tests/types/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/types/test_import_budget.py b/tests/types/test_import_budget.py new file mode 100644 index 0000000000..80e91756c1 --- /dev/null +++ b/tests/types/test_import_budget.py @@ -0,0 +1,36 @@ +"""The wire boundary stays off the package import path. + +Importing `mcp` (or `mcp.types`) must not load `mcp.types.wire` or any +per-version model module; the boundary is imported on first use only. +Asserted in a subprocess so the check observes a cold interpreter. +""" + +import subprocess +import sys + +_PROBE = """\ +import sys + +import mcp +import mcp.types + +loaded = sorted( + name for name in sys.modules if name == "mcp.types.wire" or name.startswith("mcp.types.v20") +) +if loaded: + print(",".join(loaded)) + raise SystemExit(1) +""" + + +def test_importing_mcp_does_not_load_the_wire_boundary() -> None: + """No version-keyed module may be paid for by users who never touch the + wire boundary.""" + completed = subprocess.run( + [sys.executable, "-c", _PROBE], + capture_output=True, + text=True, + timeout=60, + check=False, + ) + assert completed.returncode == 0, f"version-keyed modules loaded at import time: {completed.stdout!r}" diff --git a/tests/types/test_public_surface.py b/tests/types/test_public_surface.py new file mode 100644 index 0000000000..a6694a5a56 --- /dev/null +++ b/tests/types/test_public_surface.py @@ -0,0 +1,312 @@ +"""Import ratchet for the public type surface. + +``mcp.types.__all__`` is pinned exactly: every name exported before the +2026-07-28 protocol work stays exported (zero removals), and new names are +deliberate additions recorded here. The curated top-level ``mcp.__all__`` is +pinned unchanged. +""" + +import mcp +import mcp.types + +BASELINE_NAMES = frozenset( + [ + "Annotations", + "AudioContent", + "BaseMetadata", + "BlobResourceContents", + "CONNECTION_CLOSED", + "CallToolRequest", + "CallToolRequestParams", + "CallToolResult", + "CancelledNotification", + "CancelledNotificationParams", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteRequestParams", + "CompleteResult", + "Completion", + "CompletionArgument", + "CompletionContext", + "CompletionsCapability", + "ContentBlock", + "CreateMessageRequest", + "CreateMessageRequestParams", + "CreateMessageResult", + "CreateMessageResultWithTools", + "DEFAULT_NEGOTIATED_VERSION", + "ElicitCompleteNotification", + "ElicitCompleteNotificationParams", + "ElicitRequest", + "ElicitRequestFormParams", + "ElicitRequestParams", + "ElicitRequestURLParams", + "ElicitRequestedSchema", + "ElicitResult", + "ElicitationCapability", + "ElicitationRequiredErrorData", + "EmbeddedResource", + "EmptyResult", + "ErrorData", + "FormElicitationCapability", + "GetPromptRequest", + "GetPromptRequestParams", + "GetPromptResult", + "INTERNAL_ERROR", + "INVALID_PARAMS", + "INVALID_REQUEST", + "Icon", + "IconTheme", + "ImageContent", + "Implementation", + "IncludeContext", + "InitializeRequest", + "InitializeRequestParams", + "InitializeResult", + "InitializedNotification", + "JSONRPCError", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "LATEST_PROTOCOL_VERSION", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingCapability", + "LoggingLevel", + "LoggingMessageNotification", + "LoggingMessageNotificationParams", + "METHOD_NOT_FOUND", + "ModelHint", + "ModelPreferences", + "Notification", + "NotificationParams", + "PARSE_ERROR", + "PaginatedRequest", + "PaginatedRequestParams", + "PaginatedResult", + "PingRequest", + "ProgressNotification", + "ProgressNotificationParams", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "PromptsCapability", + "REQUEST_CANCELLED", + "REQUEST_TIMEOUT", + "ReadResourceRequest", + "ReadResourceRequestParams", + "ReadResourceResult", + "Request", + "RequestId", + "RequestParams", + "RequestParamsMeta", + "Resource", + "ResourceContents", + "ResourceLink", + "ResourceListChangedNotification", + "ResourceTemplate", + "ResourceTemplateReference", + "ResourceUpdatedNotification", + "ResourceUpdatedNotificationParams", + "ResourcesCapability", + "Result", + "Role", + "Root", + "RootsCapability", + "RootsListChangedNotification", + "SamplingCapability", + "SamplingContent", + "SamplingContextCapability", + "SamplingMessage", + "SamplingMessageContentBlock", + "SamplingToolsCapability", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "SetLevelRequestParams", + "StopReason", + "SubscribeRequest", + "SubscribeRequestParams", + "TextContent", + "TextResourceContents", + "Tool", + "ToolAnnotations", + "ToolChoice", + "ToolListChangedNotification", + "ToolResultContent", + "ToolUseContent", + "ToolsCapability", + "URL_ELICITATION_REQUIRED", + "UnsubscribeRequest", + "UnsubscribeRequestParams", + "UrlElicitationCapability", + "client_notification_adapter", + "client_request_adapter", + "client_result_adapter", + "jsonrpc_message_adapter", + "server_notification_adapter", + "server_request_adapter", + "server_result_adapter", + ] +) +"""Every name ``mcp.types.__all__`` exported before the 2026-07-28 protocol additions.""" + +ADDED_NAMES = frozenset( + [ + "CLIENT_CAPABILITIES_META_KEY", + "CLIENT_INFO_META_KEY", + "CacheableResult", + "CancelTaskRequest", + "CancelTaskRequestParams", + "CancelTaskResult", + "CreateTaskResult", + "DiscoverRequest", + "DiscoverResult", + "GetTaskPayloadRequest", + "GetTaskPayloadRequestParams", + "GetTaskPayloadResult", + "GetTaskRequest", + "GetTaskRequestParams", + "GetTaskResult", + "InputRequest", + "InputRequests", + "InputRequiredResult", + "InputResponse", + "InputResponseRequestParams", + "InputResponses", + "JSONRPC_VERSION", + "LOG_LEVEL_META_KEY", + "ListTasksRequest", + "ListTasksResult", + "MISSING_REQUIRED_CLIENT_CAPABILITY", + "MissingRequiredClientCapabilityErrorData", + "PROTOCOL_VERSION_META_KEY", + "RelatedTaskMetadata", + "ResultType", + "SubscriptionFilter", + "SubscriptionsAcknowledgedNotification", + "SubscriptionsAcknowledgedNotificationParams", + "SubscriptionsListenRequest", + "SubscriptionsListenRequestParams", + "Task", + "TaskMetadata", + "TaskStatus", + "TaskStatusNotification", + "TaskStatusNotificationParams", + "ToolExecution", + "UNSUPPORTED_PROTOCOL_VERSION", + "UnsupportedProtocolVersionErrorData", + ] +) +"""Names deliberately added to ``mcp.types.__all__`` for the 2026-07-28 protocol revision.""" + +TOP_LEVEL_NAMES = frozenset( + [ + "CallToolRequest", + "Client", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "ClientSession", + "ClientSessionGroup", + "CompleteRequest", + "CreateMessageRequest", + "CreateMessageResult", + "CreateMessageResultWithTools", + "ErrorData", + "GetPromptRequest", + "GetPromptResult", + "Implementation", + "IncludeContext", + "InitializeRequest", + "InitializeResult", + "InitializedNotification", + "JSONRPCError", + "JSONRPCRequest", + "JSONRPCResponse", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "MCPError", + "Notification", + "PingRequest", + "ProgressNotification", + "PromptsCapability", + "ReadResourceRequest", + "ReadResourceResult", + "Resource", + "ResourceUpdatedNotification", + "ResourcesCapability", + "RootsCapability", + "SamplingCapability", + "SamplingContent", + "SamplingContextCapability", + "SamplingMessage", + "SamplingMessageContentBlock", + "SamplingRole", + "SamplingToolsCapability", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "ServerSession", + "SetLevelRequest", + "StdioServerParameters", + "StopReason", + "SubscribeRequest", + "Tool", + "ToolChoice", + "ToolResultContent", + "ToolUseContent", + "ToolsCapability", + "UnsubscribeRequest", + "UrlElicitationRequiredError", + "stdio_client", + "stdio_server", + ] +) +"""The curated ``mcp.__all__`` surface, which gains nothing from the type work.""" + + +def test_no_baseline_name_removed(): + missing = BASELINE_NAMES - set(mcp.types.__all__) + assert missing == set(), f"public names removed from mcp.types.__all__: {sorted(missing)}" + + +def test_exported_surface_is_exactly_baseline_plus_recorded_additions(): + assert set(mcp.types.__all__) == BASELINE_NAMES | ADDED_NAMES + + +def test_all_has_no_duplicates(): + assert len(mcp.types.__all__) == len(set(mcp.types.__all__)) + + +def test_every_exported_name_is_importable(): + for name in mcp.types.__all__: + assert hasattr(mcp.types, name), f"mcp.types.__all__ lists {name!r} but the attribute does not exist" + + +def test_top_level_mcp_surface_unchanged(): + assert set(mcp.__all__) == TOP_LEVEL_NAMES diff --git a/tests/types/test_spec_names.py b/tests/types/test_spec_names.py new file mode 100644 index 0000000000..18de737a74 --- /dev/null +++ b/tests/types/test_spec_names.py @@ -0,0 +1,84 @@ +"""Sanity checks for the divergence map between SDK and schema names. + +Every entry in ``mcp.types._spec_names`` is a reviewed decision; these checks +keep the map honest as the type surface evolves: renames must point at real +public symbols, SDK-only names must actually be exported, and reason codes +must stay machine-greppable. + +The map is the review record; the per-version comparison machinery in +``tests/spec_oracles/`` keeps its own operational records (the harness +exemption tables and ``burndown_allowlist.json``). The cross-record checks at +the bottom assert the two agree where they overlap, so neither can drift away +from the other silently. +""" + +import re + +import mcp.types +import mcp.types.jsonrpc +from mcp.types._spec_names import SCHEMA_NOT_MODELED, SDK_ONLY_NAMES, SDK_TO_SCHEMA_RENAMES +from tests.spec_oracles._harness import SDK_MACHINERY, load_allowlist + +REASON_CODE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$") + + +def test_every_renamed_sdk_name_is_a_real_public_symbol(): + for sdk_name, schema_name in SDK_TO_SCHEMA_RENAMES.items(): + assert hasattr(mcp.types, sdk_name) or hasattr(mcp.types.jsonrpc, sdk_name) + assert schema_name and schema_name != sdk_name + + +def test_not_modeled_entries_carry_kebab_case_reason_codes(): + for schema_name, reason in SCHEMA_NOT_MODELED.items(): + assert schema_name + assert REASON_CODE.fullmatch(reason), f"{schema_name}: reason code {reason!r} is not kebab-case" + + +def test_sdk_only_names_are_exported(): + missing = SDK_ONLY_NAMES - set(mcp.types.__all__) + assert missing == set(), f"SDK-only names not in mcp.types.__all__: {sorted(missing)}" + + +def test_rename_keys_and_sdk_only_names_are_disjoint(): + assert SDK_ONLY_NAMES.isdisjoint(SDK_TO_SCHEMA_RENAMES) + + +def test_rename_schema_names_are_unique(): + """The burn-down harness inverts the rename map; duplicate schema names would collide.""" + schema_names = list(SDK_TO_SCHEMA_RENAMES.values()) + assert len(schema_names) == len(set(schema_names)) + + +def test_not_modeled_entries_agree_with_the_burndown_allowlist(): + """SCHEMA_NOT_MODELED and the allowlisted missing-type findings name the same decisions. + + Forward: a deliberately-unmodeled schema export must carry a reviewed + reason code here. Backward: an entry here must still be live in the + comparison — either it fires (and is allowlisted) as a missing type, or + its name is recycled onto a different SDK shape, in which case the + comparison pairs the two by name instead of reporting a missing type. + """ + deliberately_missing = { + entry.name + for entry in load_allowlist() + if entry.check == "SPEC-TYPE-MISSING" and entry.category == "deliberate-deviation" + } + assert deliberately_missing <= set(SCHEMA_NOT_MODELED) + for schema_name in set(SCHEMA_NOT_MODELED) - deliberately_missing: + assert hasattr(mcp.types, schema_name) or hasattr(mcp.types.jsonrpc, schema_name), ( + f"{schema_name}: not allowlisted as a missing type and not a recycled SDK name - stale entry?" + ) + + +def test_sdk_only_names_agree_with_the_burndown_records(): + """Every SDK-only name is one the burn-down also treats as schema-less. + + A name pairs with no schema def in any version exactly when the burn-down + either exempts it as machinery or carries it as an allowlisted + no-schema-counterpart finding. If a future schema revision adopts one of + these names, the corresponding allowlist entry goes stale, the burn-down + gate fails, and both records get updated together. + """ + phantom_names = {entry.name for entry in load_allowlist() if entry.check == "SDK-TYPE-PHANTOM"} + unacknowledged = SDK_ONLY_NAMES - (SDK_MACHINERY | phantom_names) + assert unacknowledged == set() diff --git a/tests/types/test_version_registry.py b/tests/types/test_version_registry.py new file mode 100644 index 0000000000..2a6dafd1ca --- /dev/null +++ b/tests/types/test_version_registry.py @@ -0,0 +1,165 @@ +"""Registry and per-version method table pins. + +The protocol-version registry and the four method tables are plain data +consumed by the wire boundary. Pinned here: the registry's ordering +invariants, spot anchors for when each method enters and leaves the protocol, +and full equality of every table against the request/notification unions of +the generated spec-oracle modules AND of the committed version modules — a +silently omitted method (or an invented one) fails both directions. +""" + +import importlib +from collections.abc import Mapping +from types import ModuleType +from typing import Any, get_args + +import pytest + +import mcp.shared.version +from mcp.shared.version import KNOWN_PROTOCOL_VERSIONS +from mcp.types import wire +from tests.spec_oracles import v2024_11_05, v2025_03_26, v2025_06_18, v2025_11_25, v2026_07_28 + +_TABLES: dict[str, Mapping[str, frozenset[str]]] = { + "client-requests": wire.CLIENT_REQUEST_METHODS, + "client-notifications": wire.CLIENT_NOTIFICATION_METHODS, + "server-requests": wire.SERVER_REQUEST_METHODS, + "server-notifications": wire.SERVER_NOTIFICATION_METHODS, +} + +_ORACLE_MODULES: dict[str, ModuleType] = { + "2024-11-05": v2024_11_05, + "2025-03-26": v2025_03_26, + "2025-06-18": v2025_06_18, + "2025-11-25": v2025_11_25, + "2026-07-28": v2026_07_28, +} + +_ORACLE_UNION_NAMES: dict[str, str] = { + "client-requests": "ClientRequest", + "client-notifications": "ClientNotification", + "server-requests": "ServerRequest", + "server-notifications": "ServerNotification", +} + +_EXCLUDED_TASK_REQUEST_METHODS = frozenset({"tasks/cancel", "tasks/get", "tasks/list", "tasks/result"}) +"""The four 2025-11-25 task request methods. + +The SDK defines the task types but never dispatches these methods, so the +tables deliberately exclude them. `notifications/tasks/status` is an ordinary +schema fact and stays listed. +""" + + +def test_wire_reexports_the_shared_registry() -> None: + """`mcp.types.wire.KNOWN_PROTOCOL_VERSIONS` is the canonical public access + to the one registry; it must be the same object, not a copy.""" + assert wire.KNOWN_PROTOCOL_VERSIONS is mcp.shared.version.KNOWN_PROTOCOL_VERSIONS + assert wire.KNOWN_PROTOCOL_VERSIONS == ("2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25", "2026-07-28") + + +def test_registry_lists_each_version_once() -> None: + """Registry position is the only ordering authority, so duplicates would + make ordering ambiguous.""" + assert len(set(KNOWN_PROTOCOL_VERSIONS)) == len(KNOWN_PROTOCOL_VERSIONS) + + +@pytest.mark.parametrize("table_name", sorted(_TABLES)) +def test_tables_cover_every_known_version_in_registry_order(table_name: str) -> None: + """Each method table has exactly one entry per known version, oldest to + newest, so a version lookup can never miss.""" + assert tuple(_TABLES[table_name]) == KNOWN_PROTOCOL_VERSIONS + + +_VERSIONS_BEFORE_2026_07_28 = ("2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25") + + +@pytest.mark.parametrize( + ("table_name", "method", "expected_versions"), + [ + # Removed in 2026-07-28: the lifecycle handshake, logging/setLevel, + # and the resources subscribe pair exist in every earlier revision. + ("client-requests", "initialize", _VERSIONS_BEFORE_2026_07_28), + ("client-notifications", "notifications/initialized", _VERSIONS_BEFORE_2026_07_28), + ("client-requests", "ping", _VERSIONS_BEFORE_2026_07_28), + ("server-requests", "ping", _VERSIONS_BEFORE_2026_07_28), + ("client-requests", "logging/setLevel", _VERSIONS_BEFORE_2026_07_28), + ("client-requests", "resources/subscribe", _VERSIONS_BEFORE_2026_07_28), + ("client-requests", "resources/unsubscribe", _VERSIONS_BEFORE_2026_07_28), + # Added in 2026-07-28. + ("client-requests", "server/discover", ("2026-07-28",)), + ("client-requests", "subscriptions/listen", ("2026-07-28",)), + ("server-notifications", "notifications/subscriptions/acknowledged", ("2026-07-28",)), + # Present in every revision. + ("client-requests", "tools/list", KNOWN_PROTOCOL_VERSIONS), + ("client-requests", "tools/call", KNOWN_PROTOCOL_VERSIONS), + ("client-requests", "prompts/list", KNOWN_PROTOCOL_VERSIONS), + ("client-requests", "prompts/get", KNOWN_PROTOCOL_VERSIONS), + ("client-requests", "resources/list", KNOWN_PROTOCOL_VERSIONS), + ("client-requests", "resources/templates/list", KNOWN_PROTOCOL_VERSIONS), + ("client-requests", "resources/read", KNOWN_PROTOCOL_VERSIONS), + ("client-requests", "completion/complete", KNOWN_PROTOCOL_VERSIONS), + # Elicitation: the request exists in 2025-06-18 and 2025-11-25 (the + # 2026-07-28 revision removed every server -> client request); the + # completion notification was added in 2025-11-25 and survives. + ("server-requests", "elicitation/create", ("2025-06-18", "2025-11-25")), + ("server-notifications", "notifications/elicitation/complete", ("2025-11-25", "2026-07-28")), + # Sampling and roots: server -> client traffic through 2025-11-25. + ("server-requests", "sampling/createMessage", _VERSIONS_BEFORE_2026_07_28), + ("server-requests", "roots/list", _VERSIONS_BEFORE_2026_07_28), + ("client-notifications", "notifications/roots/list_changed", _VERSIONS_BEFORE_2026_07_28), + ], +) +def test_method_version_windows(table_name: str, method: str, expected_versions: tuple[str, ...]) -> None: + """Spot anchors for the revisions where each method exists, per the + published schemas; a method outside its window classifies as unknown.""" + table = _TABLES[table_name] + present = tuple(version for version in KNOWN_PROTOCOL_VERSIONS if method in table[version]) + assert present == expected_versions + + +def test_no_server_requests_at_2026_07_28() -> None: + """The 2026-07-28 revision removed the server -> client request channel + entirely.""" + assert wire.SERVER_REQUEST_METHODS["2026-07-28"] == frozenset() + + +def _union_method_literals(union: Any) -> frozenset[str]: + """Collect the `method` literal of every arm of an oracle union. + + `None` means the union does not exist in that revision's schema (the + 2026-07-28 schema removed every server -> client request, so its oracle + module defines no ServerRequest alias): no methods. + """ + if union is None: + return frozenset() + methods: set[str] = set() + for arm in get_args(union): + (literal,) = get_args(arm.model_fields["method"].annotation) + assert isinstance(literal, str) + methods.add(literal) + return frozenset(methods) + + +@pytest.mark.parametrize("table_name", sorted(_TABLES)) +@pytest.mark.parametrize("version", KNOWN_PROTOCOL_VERSIONS) +def test_tables_equal_the_oracle_union_methods(version: str, table_name: str) -> None: + """Full equality, both directions, against the generated oracle unions: + a silently omitted method (or an invented one) fails here. The only + deliberate difference is the four task request methods.""" + oracle_union = getattr(_ORACLE_MODULES[version], _ORACLE_UNION_NAMES[table_name], None) + expected = _union_method_literals(oracle_union) - _EXCLUDED_TASK_REQUEST_METHODS + assert _TABLES[table_name][version] == expected + + +@pytest.mark.parametrize("table_name", sorted(_TABLES)) +@pytest.mark.parametrize("version", KNOWN_PROTOCOL_VERSIONS) +def test_tables_equal_the_version_module_union_methods(version: str, table_name: str) -> None: + """Full equality, both directions, against the committed version modules' + direction unions — the same sets the wire boundary validates emissions + through. The only deliberate difference is the four task request + methods.""" + module = importlib.import_module(f"mcp.types.v{version.replace('-', '_')}") + union = getattr(module, _ORACLE_UNION_NAMES[table_name], None) + expected = _union_method_literals(union) - _EXCLUDED_TASK_REQUEST_METHODS + assert _TABLES[table_name][version] == expected diff --git a/tests/types/test_version_surfaces.py b/tests/types/test_version_surfaces.py new file mode 100644 index 0000000000..06455858bc --- /dev/null +++ b/tests/types/test_version_surfaces.py @@ -0,0 +1,693 @@ +"""Each committed version module's effective surface equals its pinned-schema oracle. + +The modules under ``src/mcp/types/v*`` hold wire-shape models in delta form: a +module defines what its protocol revision added or changed and imports the +rest from the version module that last defined it. These tests pin, per +version: the exported surface (exactly the schema's named definitions), every +definition's wire shape (recursively through the classes a field annotation +actually references, so a stale inherited reference cannot hide behind a +matching name), the import manifest's defining-module claims, the absence of +shadowed and orphan definitions, and the recorded removal set. + +The oracle modules under ``tests/spec_oracles`` are regenerated verbatim from +the pinned schemas; the deliberate scaffold deltas are the annotated tolerance +tables below, and everything else must match exactly. +""" + +from __future__ import annotations + +import ast +import importlib +import re +from pathlib import Path +from types import ModuleType, UnionType +from typing import Annotated, Any, Literal, Union, get_args, get_origin + +import pytest +from pydantic import BaseModel +from pydantic_core import PydanticUndefined +from typing_extensions import TypeAliasType + +# Protocol revisions whose version modules are committed, oldest first; later +# revisions join this list as their modules land. +MODELED_VERSIONS: tuple[str, ...] = ("2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25", "2026-07-28") + +# Tolerance: value-transforming pydantic types are downgraded to plain ``str`` +# in the version modules — URL normalization and base64 re-encoding would +# change wire bytes on a validate -> re-dump round trip. (``Base64Str`` needs +# no entry: it is ``Annotated[str, ...]``, so both sides compare as ``str``.) +_VALUE_DOWNGRADES = {"AnyUrl": "str", "FileUrl": "str"} + +# Tolerance: the version modules widen ``structuredContent`` from +# ``dict[str, Any]`` to ``Any`` — the newest schema types the field ``Any``, +# and the wire models never narrow a value the SDK models accept. +_WIDENED_FIELDS = frozenset({"structured_content"}) + +# Tolerance: the pinned 2026-07-28 schema.json renders JSONValue's primitive +# branch as ["string", "integer", "boolean"], but its schema.ts source defines +# all six JSON types (string | number | boolean | null | object | array). The +# oracle reproduces the render verbatim; the version module follows the +# schema.ts definition so fractional numbers and nulls validate. The module +# alias is pinned here verbatim, so any further drift still fails. +_ALIAS_OVERRIDES: dict[tuple[str, str], str] = { + ("v2026_07_28", "JSONValue"): "JSONObject | list[JSONValue] | str | int | float | bool | None", +} + +# Tolerance: the pinned schema.json renderings type a few more schema.ts +# `number` positions as "integer" — the same render artifact fixed for the +# JSONValue alias. The version modules keep the int arm and gain a float arm +# at exactly these positions, so fractional elicitation answers and +# number-schema bounds have a wire form; the oracles keep the render +# verbatim, and the module annotation is pinned here verbatim so any further +# drift still fails. (Defining module, class, field) -> the exact module +# annotation. +_RENDER_ARTIFACT_WIDENED: dict[tuple[str, str, str], Any] = { + ("v2025_06_18", "ElicitResult", "content"): dict[str, str | int | float | bool] | None, + ("v2025_06_18", "NumberSchema", "maximum"): int | float | None, + ("v2025_06_18", "NumberSchema", "minimum"): int | float | None, + ("v2025_11_25", "ElicitResult", "content"): dict[str, list[str] | str | int | float | bool] | None, + ("v2025_11_25", "NumberSchema", "default"): int | float | None, + ("v2025_11_25", "NumberSchema", "maximum"): int | float | None, + ("v2025_11_25", "NumberSchema", "minimum"): int | float | None, + ("v2026_07_28", "ElicitResult", "content"): dict[str, list[str] | str | int | float | bool] | None, +} + +# The closure for every OTHER module field that admits int but not float: +# the integer rendering is the intended type, justified per field name by the +# spec fact in plain words. A field absent here whose annotation is +# int-without-float fails test_int_only_number_positions_are_classified, so a +# future render artifact cannot land unexamined. +_INTENDED_INTEGER_FIELDS: dict[str, str] = { + "id": "JSON-RPC ids: every schema rendering pins the numeric kind to integer; fractional ids are not interoperable", + "request_id": "a cancelled/targeted request id mirrors the JSON-RPC id type (string or integer)", + "progress_token": "progress tokens mirror the JSON-RPC id type (string or integer)", + "code": "JSON-RPC 2.0 defines error codes as integers", + "total": "a count of completion values; the superset model agrees (int)", + "max_tokens": "a token count; the superset model agrees (int)", + "size": "a resource size in bytes; the superset model agrees (int)", + "ttl": "a task time-to-live in milliseconds; the superset model agrees (int)", + "ttl_ms": "a cache time-to-live in milliseconds; the superset model agrees (int)", + "poll_interval": "a polling interval in milliseconds; the superset model agrees (int)", + "max_length": "JSON Schema maxLength is a non-negative integer keyword", + "min_length": "JSON Schema minLength is a non-negative integer keyword", + "max_items": "JSON Schema maxItems is a non-negative integer keyword", + "min_items": "JSON Schema minItems is a non-negative integer keyword", +} + +# Tolerance: content blocks carried into modules older than the schema that +# introduces them, so emitting such a block to an older peer passes it through +# instead of refusing. Maps module -> {class name -> introducing oracle}; the +# carried class is pinned against that oracle in +# test_carried_content_blocks_match_the_introducing_version, and dropped from +# union comparisons against this version's own oracle. +CARRIED_CONTENT_BLOCKS: dict[str, dict[str, str]] = { + "v2024_11_05": {"AudioContent": "v2025_03_26", "ResourceLink": "v2025_06_18"}, + "v2025_03_26": {"ResourceLink": "v2025_06_18"}, +} + +# The 2024-11-05 schema names a definition ``Annotated``, which would shadow +# ``typing.Annotated`` in a Python module; the generator resolves the +# collision by appending ``Model``, on the oracle and version-module sides +# alike. The schema's named surface maps through this table everywhere. +_COLLISION_RENAMES = {"Annotated": "AnnotatedModel"} + +# Tool input/output schema interiors stay ``extra="allow"``: at every revision +# these declare only a subset of JSON Schema keywords and the rest must ride +# extra fields through revalidation. +_OPEN_INTERIOR_ALIASES = frozenset({"inputSchema", "outputSchema"}) + +Sig = tuple[Any, ...] + + +def _module_name(version: str) -> str: + return "v" + version.replace("-", "_") + + +def _package(version: str) -> ModuleType: + return importlib.import_module(f"mcp.types.{_module_name(version)}") + + +def _oracle(version: str) -> ModuleType: + return importlib.import_module(f"tests.spec_oracles.{_module_name(version)}") + + +def _surface(version: str) -> set[str]: + """The schema's named definitions for a version, as module attribute names.""" + spec_defs: tuple[str, ...] = _oracle(version).SPEC_DEFS + return {_COLLISION_RENAMES.get(name, name) for name in spec_defs} + + +def _local_classes(mod: ModuleType) -> dict[str, type[BaseModel]]: + """Model classes a module itself defines (inherited names excluded).""" + return { + name: obj + for name, obj in vars(mod).items() + if isinstance(obj, type) and issubclass(obj, BaseModel) and obj.__module__ == mod.__name__ + } + + +def _alias_value(obj: Any) -> Any: + """A lazy alias's value, or the object itself for eager aliases.""" + return obj.__value__ if isinstance(obj, TypeAliasType) else obj + + +def _models_in(annotation: Any) -> dict[str, type[BaseModel]]: + """Model classes appearing anywhere in an annotation, keyed by class name.""" + if isinstance(annotation, type) and issubclass(annotation, BaseModel): + return {annotation.__name__: annotation} + found: dict[str, type[BaseModel]] = {} + for arg in get_args(annotation): + found.update(_models_in(arg)) + return found + + +def _synthetic_renames(oracle: ModuleType) -> dict[str, str]: + """Oracle synthetic class name -> version-module class name. + + Mirrors the generator's deterministic-naming pass: a synthesized + ``Params``/``Meta`` class referenced by exactly one field of one + owner is named ``Params``/``Meta``; shared synthetics keep + their generated names. + """ + classes = _local_classes(oracle) + synthetic = {name for name in classes if re.fullmatch(r"(?:Params|Meta)\d*", name)} + references: dict[str, list[tuple[str, str]]] = {name: [] for name in synthetic} + for owner_name, cls in classes.items(): + for field_name, info in cls.model_fields.items(): + for name in synthetic & set(_models_in(info.annotation)): + references[name].append((owner_name, field_name)) + renames: dict[str, str] = {} + taken = set(classes) + for name in sorted(synthetic): + refs = references[name] + if len(refs) != 1: + continue + owner, field_name = refs[0] + suffix = {"params": "Params", "meta": "Meta"}.get(field_name) + if suffix is None: + continue + target = f"{owner}{suffix}" + if target in taken: + continue + renames[name] = target + taken.add(target) + return renames + + +def _sig(annotation: Any, *, rename: dict[str, str], drop: frozenset[str], widen_dicts: bool = False) -> Sig: + """Canonicalize an annotation into a comparable signature tuple. + + Classes appear by name (mapped through ``rename``), unions as unordered + member sets. ``drop`` removes the carried content-block arms from + version-module unions; ``widen_dicts`` collapses ``dict[str, Any]`` to + ``Any`` for the widened-field tolerance. + """ + if annotation is None or annotation is type(None): + return ("none",) + if annotation is Any: + return ("any",) + if isinstance(annotation, TypeAliasType): + return ("aliasref", annotation.__name__) + origin = get_origin(annotation) + if origin is Annotated: + return _sig(get_args(annotation)[0], rename=rename, drop=drop, widen_dicts=widen_dicts) + if origin is Literal: + return ("literal", frozenset(get_args(annotation))) + if origin is Union or origin is UnionType: + members = frozenset( + _sig(arg, rename=rename, drop=drop, widen_dicts=widen_dicts) + for arg in get_args(annotation) + if not (isinstance(arg, type) and arg.__name__ in drop) + ) + if len(members) == 1: + return next(iter(members)) + return ("union", members) + if origin is dict: + key, value = (_sig(arg, rename=rename, drop=drop, widen_dicts=widen_dicts) for arg in get_args(annotation)) + if widen_dicts and key == ("cls", "str") and value == ("any",): + return ("any",) + return ("dict", key, value) + if origin is not None: + args = tuple(_sig(arg, rename=rename, drop=drop, widen_dicts=widen_dicts) for arg in get_args(annotation)) + return ("generic", origin.__name__, args) + if isinstance(annotation, type): + return ("cls", rename.get(annotation.__name__, annotation.__name__)) + return ("opaque", repr(annotation)) + + +def _assert_models_match( + oracle_cls: type[BaseModel], + package_cls: type[BaseModel], + *, + rename: dict[str, str], + drop: frozenset[str], + context: str, + seen: set[tuple[int, int]], + recurse: bool = True, + overrides: dict[tuple[str, str], Any] | None = None, +) -> None: + """Field-level comparison, recursing into the classes annotations reference. + + Recursion follows the annotation's actual class objects — not the module + namespace — so an inherited definition whose reference closure went stale + fails here even when every name still resolves. The synthesized helper + classes (anonymous nested schema objects) are compared exactly this way: + they are reachable only through the named definitions that reference them. + + ``overrides`` maps (class name, field name) to the verbatim module + annotation pinned for it (the render-artifact widenings); those fields are + compared against the pin instead of the oracle's rendered annotation. + """ + pair = (id(oracle_cls), id(package_cls)) + if pair in seen: + return + seen.add(pair) + oracle_fields = oracle_cls.model_fields + package_fields = package_cls.model_fields + assert set(package_fields) == set(oracle_fields), f"{context}: field set differs" + for field_name, oracle_info in oracle_fields.items(): + package_info = package_fields[field_name] + assert package_info.alias == oracle_info.alias, f"{context}.{field_name}: wire alias differs" + assert package_info.is_required() == oracle_info.is_required(), f"{context}.{field_name}: requiredness differs" + if not oracle_info.is_required() and oracle_info.default is not PydanticUndefined: + assert package_info.default == oracle_info.default, f"{context}.{field_name}: default differs" + widen = field_name in _WIDENED_FIELDS + package_sig = _sig(package_info.annotation, rename={}, drop=drop, widen_dicts=widen) + if overrides is not None and (package_cls.__name__, field_name) in overrides: + pinned_sig = _sig(overrides[package_cls.__name__, field_name], rename={}, drop=frozenset()) + assert package_sig == pinned_sig, f"{context}.{field_name}: annotation differs from its pinned widening" + continue + oracle_sig = _sig(oracle_info.annotation, rename=rename, drop=frozenset(), widen_dicts=widen) + assert package_sig == oracle_sig, f"{context}.{field_name}: annotation differs" + if not recurse: + continue + package_models = _models_in(package_info.annotation) + for oracle_ref_name, oracle_ref in _models_in(oracle_info.annotation).items(): + target = rename.get(oracle_ref_name, oracle_ref_name) + package_ref = package_models.get(target) + assert package_ref is not None, f"{context}.{field_name}: no referenced class {target}" + _assert_models_match( + oracle_ref, + package_ref, + rename=rename, + drop=drop, + context=f"{context}.{field_name}->{target}", + seen=seen, + overrides=overrides, + ) + + +def _versions_by_module(module: str) -> str: + return module.removeprefix("v").replace("_", "-") + + +def _manifest_imports(version: str) -> dict[str, list[str]]: + """Defining-module -> imported names, parsed from the module source.""" + tree = ast.parse(Path(_package(version).__file__ or "").read_text()) + manifest: dict[str, list[str]] = {} + for node in tree.body: + if isinstance(node, ast.ImportFrom) and (node.module or "").startswith("mcp.types.v"): + module = (node.module or "").removeprefix("mcp.types.") + manifest.setdefault(module, []).extend(alias.name for alias in node.names) + return manifest + + +def _top_level_definitions(version: str) -> set[str]: + """Names a version module defines at top level (imports excluded).""" + tree = ast.parse(Path(_package(version).__file__ or "").read_text()) + names: set[str] = set() + for node in tree.body: + if isinstance(node, ast.ClassDef): + names.add(node.name) + elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name): + names.add(node.target.id) + elif isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name): + names.add(node.targets[0].id) + return names - {"__all__", "REMOVED_FROM_PREVIOUS_VERSION"} + + +def _referenced_names(version: str) -> dict[str, set[str]]: + """Per top-level definition, the sibling names its body mentions. + + String annotations count: with lazy annotation evaluation every reference + can hide inside a string, so each string constant that parses as an + expression contributes its names. + """ + tree = ast.parse(Path(_package(version).__file__ or "").read_text()) + local = _top_level_definitions(version) + refs: dict[str, set[str]] = {} + for node in tree.body: + if isinstance(node, ast.ClassDef): + name = node.name + elif isinstance(node, ast.AnnAssign | ast.Assign): + target = node.target if isinstance(node, ast.AnnAssign) else node.targets[0] + if not isinstance(target, ast.Name) or target.id in {"__all__", "REMOVED_FROM_PREVIOUS_VERSION"}: + continue + name = target.id + else: + continue + found: set[str] = set() + for sub in ast.walk(node): + if isinstance(sub, ast.Name) and sub.id in local: + found.add(sub.id) + elif isinstance(sub, ast.Constant) and isinstance(sub.value, str): + try: + expr = ast.parse(sub.value, mode="eval") + except SyntaxError: + continue + found.update(n.id for n in ast.walk(expr) if isinstance(n, ast.Name) and n.id in local) + refs[name] = found - {name} + return refs + + +@pytest.mark.parametrize("version", MODELED_VERSIONS) +def test_module_surface_equals_the_pinned_schema_surface(version: str) -> None: + """``__all__`` is exactly the schema's named definitions, both directions.""" + package = _package(version) + exported = set(package.__all__) + assert len(package.__all__) == len(exported), f"{version}: duplicate names in __all__" + assert exported == _surface(version) + for name in exported: + assert getattr(package, name, None) is not None, f"{version}: {name} listed but not resolvable" + + +@pytest.mark.parametrize("version", MODELED_VERSIONS) +def test_every_definition_matches_its_oracle_shape(version: str) -> None: + """Every named definition matches its oracle, recursively through helpers. + + The version-module counterpart is resolved through the module's effective + namespace, then compared recursively through the actual annotation + referents, so both a missing redefinition and a stale inherited closure + fail. The synthesized helper classes are covered by the recursion: each is + reachable from the named definitions that reference it, and an inherited + name is compared as the object the inherited annotation really points at. + """ + oracle = _oracle(version) + package = _package(version) + rename = {**_synthetic_renames(oracle), **_COLLISION_RENAMES, **_VALUE_DOWNGRADES} + drop = frozenset(CARRIED_CONTENT_BLOCKS.get(_module_name(version), {})) + overrides = { + (class_name, field_name): annotation + for (module, class_name, field_name), annotation in _RENDER_ARTIFACT_WIDENED.items() + if module == _module_name(version) + } + seen: set[tuple[int, int]] = set() + for target in sorted(_surface(version)): + oracle_obj = getattr(oracle, target) + package_obj = getattr(package, target, None) + assert package_obj is not None, f"{version}: oracle definition {target} missing" + context = f"{version}.{target}" + if isinstance(oracle_obj, type) and issubclass(oracle_obj, BaseModel): + assert isinstance(package_obj, type) and issubclass(package_obj, BaseModel), f"{context}: not a model" + _assert_models_match( + oracle_obj, package_obj, rename=rename, drop=drop, context=context, seen=seen, overrides=overrides + ) + continue + override = _ALIAS_OVERRIDES.get((_module_name(version), target)) + if override is not None: + assert _alias_value(package_obj) == override, f"{context}: widened alias drifted" + continue + oracle_value = _alias_value(oracle_obj) + package_value = _alias_value(package_obj) + oracle_sig = _sig(oracle_value, rename=rename, drop=frozenset()) + package_sig = _sig(package_value, rename={}, drop=drop) + assert package_sig == oracle_sig, f"{context}: alias value differs" + package_models = _models_in(package_value) + for ref_name, oracle_ref in _models_in(oracle_value).items(): + ref_target = rename.get(ref_name, ref_name) + package_ref = package_models.get(ref_target) + assert package_ref is not None, f"{context}: no referenced class {ref_target}" + _assert_models_match( + oracle_ref, + package_ref, + rename=rename, + drop=drop, + context=f"{context}->{ref_target}", + seen=seen, + overrides=overrides, + ) + + +@pytest.mark.parametrize( + ("version", "block", "introducing"), + [ + ("2024-11-05", "AudioContent", "2025-03-26"), + ("2024-11-05", "ResourceLink", "2025-06-18"), + ("2025-03-26", "ResourceLink", "2025-06-18"), + ], +) +def test_carried_content_blocks_match_the_introducing_version(version: str, block: str, introducing: str) -> None: + """A carried content block is shape-faithful to the schema that introduces it. + + Shallow on purpose: the carried definition keeps its own revision's + reference closure (its ``Annotations`` is this version's, not the + introducing version's), so referenced classes compare by name only here + and recursively in the per-version oracle comparison. + """ + package_cls = _local_classes(_package(version))[block] + oracle_cls = _local_classes(_oracle(introducing))[block] + _assert_models_match( + oracle_cls, + package_cls, + rename=dict(_VALUE_DOWNGRADES), + drop=frozenset(), + context=f"{version}.{block} (vs the {introducing} oracle)", + seen=set(), + recurse=False, + ) + + +@pytest.mark.parametrize("version", MODELED_VERSIONS) +def test_extra_policy_is_closed_except_the_enumerated_open_classes(version: str) -> None: + """Locally defined classes are closed except the enumerated open classes. + + Open by design: the ``_meta`` carriers (unknown ``_meta`` keys must + survive revalidation), the tool input/output schema interiors, and the + subscription filter. Everything else is ``extra="ignore"`` so a field the + target revision never defined registers as a loss on revalidation. + """ + package = _package(version) + classes = _local_classes(package) + expected_open: set[str] = {"SubscriptionFilter"} & set(classes) + for cls in classes.values(): + for field_name, info in cls.model_fields.items(): + alias = info.alias or field_name + if alias == "_meta" or alias in _OPEN_INTERIOR_ALIASES: + expected_open.update(name for name in _models_in(info.annotation) if name in classes) + for name, cls in classes.items(): + expected = "allow" if name in expected_open else "ignore" + assert cls.model_config.get("extra") == expected, f"{version}.{name}: extra={cls.model_config.get('extra')!r}" + assert cls.model_config.get("populate_by_name") is True, f"{version}.{name}: populate_by_name is not set" + + +def _namespace_classes(mod: ModuleType) -> dict[str, type[BaseModel]]: + """Every model class in a module's effective namespace, inherited or local.""" + return {name: obj for name, obj in vars(mod).items() if isinstance(obj, type) and issubclass(obj, BaseModel)} + + +def _admits_int_without_float(annotation: Any, seen: frozenset[int] = frozenset()) -> bool: + """True when ``annotation`` admits int somewhere without a float sibling. + + Walks unions, containers, Annotated metadata, and lazy aliases (cycle-safe + via ``seen``); Literal values are exact constants, never an int admission. + """ + if isinstance(annotation, TypeAliasType): + if id(annotation) in seen: + return False + return _admits_int_without_float(annotation.__value__, seen | {id(annotation)}) + origin = get_origin(annotation) + if origin is Annotated: + return _admits_int_without_float(get_args(annotation)[0], seen) + if origin is Literal: + return False + if origin is Union or origin is UnionType: + members = get_args(annotation) + if int in members and float not in members: + return True + return any(_admits_int_without_float(member, seen) for member in members if member is not int) + if origin is not None: + return any(_admits_int_without_float(arg, seen) for arg in get_args(annotation)) + return annotation is int + + +@pytest.mark.parametrize("version", MODELED_VERSIONS) +def test_int_only_number_positions_are_classified(version: str) -> None: + """Every module field position that admits int but not float is claimed + by exactly one pinned table: the render-artifact widenings (the module + must carry the float arm the schema.json rendering lost — schema.ts types + those positions number) or the intended-integer field closure (the spec + fact really is integral). An unclassified position fails, so a new + integer rendering cannot land unexamined; a widened position that loses + its float arm fails too. Positions key on the class's defining module — + in the delta layout an inherited class is the defining module's object.""" + for cls in _namespace_classes(_package(version)).values(): + home = cls.__module__.removeprefix("mcp.types.") + for field_name, info in cls.model_fields.items(): + int_only = _admits_int_without_float(info.annotation) + if (home, cls.__name__, field_name) in _RENDER_ARTIFACT_WIDENED: + assert not int_only, f"{version}.{cls.__name__}.{field_name}: pinned widening lost its float arm" + elif int_only: + assert field_name in _INTENDED_INTEGER_FIELDS, ( + f"{version}.{cls.__name__}.{field_name}: int-without-float position is neither a pinned " + "render-artifact widening nor a pinned intended-integer field" + ) + + +@pytest.mark.parametrize("version", MODELED_VERSIONS) +def test_manifest_imports_name_the_defining_module(version: str) -> None: + """Every import line names the module that really last defined the name. + + For classes the defining module is recorded on the object; for aliases the + named module's source must contain the defining assignment. In both cases + no module strictly between the named definer and this one may redefine the + name — an import from a too-old module is wrong even when the shapes + happen to coincide. + """ + package = _package(version) + modeled_modules = [_module_name(v) for v in MODELED_VERSIONS] + for definer, names in _manifest_imports(version).items(): + assert definer in modeled_modules, f"{version}: manifest imports from uncommitted module {definer}" + definer_definitions = _top_level_definitions(_versions_by_module(definer)) + between = modeled_modules[modeled_modules.index(definer) + 1 : modeled_modules.index(_module_name(version))] + for name in names: + obj = getattr(package, name) + if isinstance(obj, type) and issubclass(obj, BaseModel): + assert obj.__module__ == f"mcp.types.{definer}", f"{version}.{name}: defined in {obj.__module__}" + else: + assert name in definer_definitions, f"{version}.{name}: {definer} does not define it" + redefiners = [m for m in between if name in _top_level_definitions(_versions_by_module(m))] + assert not redefiners, f"{version}.{name}: redefined after {definer} in {redefiners}" + + +@pytest.mark.parametrize("version", MODELED_VERSIONS) +def test_names_are_exactly_one_of_imported_or_defined(version: str) -> None: + """No silent shadowing: a name is either imported in the manifest or defined below, never both.""" + defined = _top_level_definitions(version) + imported = {name for names in _manifest_imports(version).values() for name in names} + shadowed = defined & imported + assert not shadowed, f"{version}: defined names shadow manifest imports: {sorted(shadowed)}" + for name in _package(version).__all__: + assert name in defined or name in imported, f"{version}.{name}: neither imported nor defined" + + +@pytest.mark.parametrize("version", MODELED_VERSIONS) +def test_no_orphan_definitions(version: str) -> None: + """Every local definition outside ``__all__`` earns its place. + + A non-exported definition must be a helper reachable from an exported + definition of this module, or a carried content block (defined in modules + older than the schema that introduces it so emission passes the block + through un-gated). + """ + package = _package(version) + defined = _top_level_definitions(version) + exported = set(package.__all__) + carried = set(CARRIED_CONTENT_BLOCKS.get(_module_name(version), {})) + refs = _referenced_names(version) + reachable: set[str] = set() + frontier = list(defined & exported) + while frontier: + name = frontier.pop() + for ref in refs.get(name, set()): + if ref in defined and ref not in reachable: + reachable.add(ref) + frontier.append(ref) + orphans = defined - exported - carried - reachable + assert not orphans, f"{version}: definitions nothing exports or references: {sorted(orphans)}" + + +# A lazy alias like the oracles use for recursive definitions; only the name +# rides into its signature. +_LazyAlias = TypeAliasType("_LazyAlias", int) + +# Alias objects as test data for the int-admission walker. +_StrAlias = TypeAliasType("_StrAlias", str) + + +def test_int_admission_walker_resolves_aliases() -> None: + assert _admits_int_without_float(_LazyAlias) + assert not _admits_int_without_float(_StrAlias) + + +def test_int_admission_walker_stops_on_alias_cycles() -> None: + # A self-referential alias is walked once; revisiting it resolves False. + assert not _admits_int_without_float(_LazyAlias, frozenset({id(_LazyAlias)})) + + +def test_int_admission_walker_treats_literal_values_as_constants() -> None: + assert not _admits_int_without_float(Literal[0, 1]) + + +def test_int_admission_walker_unwraps_annotated_metadata() -> None: + # pydantic strips Annotated from model_fields annotations, but raw + # annotations passed to the walker may still carry it. + assert _admits_int_without_float(Annotated[int, "wire-metadata"]) + assert not _admits_int_without_float(Annotated[float, "wire-metadata"]) + + +def test_int_admission_walker_sees_a_float_sibling() -> None: + assert not _admits_int_without_float(int | float | None) + assert _admits_int_without_float(dict[str, str | int | bool] | None) + + +def test_annotation_signatures_cover_forms_later_revisions_add() -> None: + """The signature canonicalizer handles annotation forms the two oldest revisions never produce. + + Later schema revisions add lazy (recursive) aliases, the widened + ``structuredContent`` dict, and carried arms whose removal collapses a + union to one member; nested ``Annotated`` metadata and non-type metadata + objects can appear anywhere. Pinning these here keeps the comparison + machinery trustworthy before the modules that need it land. + """ + assert _sig(_LazyAlias, rename={}, drop=frozenset()) == ("aliasref", "_LazyAlias") + assert _sig(Annotated[int, "wire-metadata"], rename={}, drop=frozenset()) == ("cls", "int") + + class Carried(BaseModel): + pass + + assert _sig(str | Carried, rename={}, drop=frozenset({"Carried"})) == ("cls", "str") + assert _sig(dict[str, Any], rename={}, drop=frozenset(), widen_dicts=True) == ("any",) + assert _sig(dict[str, Any], rename={}, drop=frozenset()) == ("dict", ("cls", "str"), ("any",)) + assert _sig(123, rename={}, drop=frozenset()) == ("opaque", "123") + + +def test_synthetic_rename_derivation_skips_unnameable_helpers() -> None: + """A synthetic helper keeps its generated name when no derived name fits. + + The derivation only renames a helper referenced by exactly one ``params`` + or ``meta`` field whose derived ``Params``/``Meta`` name is + free; a helper hanging off another field, or whose derived name is taken, + keeps its generated name on both sides of the comparison. + """ + fake = ModuleType("fake_oracle") + + class Params1(BaseModel): + pass + + class Params2(BaseModel): + pass + + class Owner(BaseModel): + result: Params1 | None = None + params: Params2 | None = None + + class OwnerParams(BaseModel): + pass + + for cls in (Params1, Params2, Owner, OwnerParams): + cls.__module__ = "fake_oracle" + setattr(fake, cls.__name__, cls) + assert _synthetic_renames(fake) == {} + + +@pytest.mark.parametrize("version", MODELED_VERSIONS) +def test_removed_names_match_the_oracle_diff(version: str) -> None: + """The removal record equals the oracle surface diff against the predecessor.""" + package = _package(version) + index = MODELED_VERSIONS.index(version) + if index == 0: + assert not hasattr(package, "REMOVED_FROM_PREVIOUS_VERSION") + return + removed = package.REMOVED_FROM_PREVIOUS_VERSION + assert isinstance(removed, frozenset) + assert removed == _surface(MODELED_VERSIONS[index - 1]) - _surface(version) diff --git a/tests/types/test_wire_boundary.py b/tests/types/test_wire_boundary.py new file mode 100644 index 0000000000..f85fc0b957 --- /dev/null +++ b/tests/types/test_wire_boundary.py @@ -0,0 +1,850 @@ +"""Emission facts of the wire boundary, one (+)/(-) pair per version-keyed rule. + +Each test names the spec fact it pins in plain words, with its provenance +class: spec-mandated (the published schema or spec prose requires it) or +deployed-peer-mandated (a behavior real deployed SDKs enforce on the wire). +Negative tests assert byte identity against the plain monolith dump via +``json.dumps`` so key order is part of the guarantee. +""" + +from __future__ import annotations + +import json +from typing import Any, cast + +import pytest +from pydantic import BaseModel, FileUrl, ValidationError + +from mcp.types import ( + CLIENT_CAPABILITIES_META_KEY, + CLIENT_INFO_META_KEY, + LOG_LEVEL_META_KEY, + PROTOCOL_VERSION_META_KEY, + AudioContent, + CallToolRequest, + CallToolRequestParams, + CallToolResult, + CancelledNotification, + CancelledNotificationParams, + ClientCapabilities, + CompleteResult, + Completion, + CreateMessageRequest, + CreateMessageRequestParams, + CreateMessageResult, + CreateMessageResultWithTools, + DiscoverResult, + ElicitCompleteNotification, + ElicitCompleteNotificationParams, + ElicitRequest, + ElicitRequestFormParams, + ElicitRequestURLParams, + ElicitResult, + EmptyResult, + ErrorData, + GetPromptResult, + Icon, + Implementation, + InitializedNotification, + InitializeRequest, + InitializeRequestParams, + InputRequiredResult, + JSONRPCError, + JSONRPCNotification, + JSONRPCRequest, + JSONRPCResponse, + ListRootsRequest, + ListRootsResult, + ListToolsRequest, + ListToolsResult, + LoggingMessageNotification, + LoggingMessageNotificationParams, + PaginatedRequestParams, + PingRequest, + ProgressNotification, + ProgressNotificationParams, + PromptMessage, + RequestParamsMeta, + ResourceLink, + Root, + RootsCapability, + SamplingMessage, + ServerCapabilities, + SubscribeRequest, + SubscribeRequestParams, + SubscriptionFilter, + SubscriptionsListenRequest, + SubscriptionsListenRequestParams, + TaskMetadata, + TextContent, + Tool, + ToolUseContent, +) +from mcp.types.wire import ( + UnknownProtocolVersionError, + UnsupportedAtVersionError, + _merge_and_align, # tested directly: the defect guard is unreachable via valid packages + serialize_for, +) + +V1 = "2024-11-05" +V2 = "2025-03-26" +V3 = "2025-06-18" +V4 = "2025-11-25" +D = "2026-07-28" +RELEASED = (V1, V2, V3, V4) + + +def monolith_dump(model: BaseModel) -> dict[str, Any]: + """The plain user dump — the byte-identity reference for released versions.""" + return model.model_dump(by_alias=True, mode="json", exclude_none=True) + + +def as_bytes(payload: dict[str, Any]) -> str: + return json.dumps(payload, sort_keys=False) + + +def identity_meta() -> dict[str, Any]: + """The caller-supplied session identity a 2026-07-28 client request needs.""" + return { + CLIENT_INFO_META_KEY: {"name": "example-client", "version": "1.0.0"}, + CLIENT_CAPABILITIES_META_KEY: {}, + } + + +def as_meta(entries: dict[str, Any]) -> RequestParamsMeta: + """Build a params _meta value from plain entries (the open-map wire form).""" + return cast("RequestParamsMeta", entries) + + +# --- payload domain and version registry --------------------------------- + + +@pytest.mark.parametrize( + "fragment", + [ + TextContent(text="x"), + ClientCapabilities(), + SamplingMessage(role="user", content=TextContent(text="x")), + PaginatedRequestParams(), + ], + ids=lambda fragment: type(fragment).__name__, +) +def test_serialize_for_refuses_bare_fragments(fragment: BaseModel) -> None: + """Fragments are shaped only inside the body that carries them; a bare + fragment is a programming error, refused identically at every version.""" + with pytest.raises(TypeError, match="message body or an envelope model"): + serialize_for(fragment, V4) + + +def test_bare_fragment_refused_before_the_version_check() -> None: + """Argument validation precedes version lookup: (bare fragment, unknown + version) deterministically raises TypeError.""" + with pytest.raises(TypeError): + serialize_for(TextContent(text="x"), "not-a-version") + + +def test_serialize_for_unknown_version() -> None: + """Emission never guesses a wire shape for a version it does not know.""" + with pytest.raises(UnknownProtocolVersionError) as exc_info: + serialize_for(EmptyResult(), "2030-01-01") + assert exc_info.value.version == "2030-01-01" + assert exc_info.value.known == (V1, V2, V3, V4, D) + + +# --- envelope frames (version-independent) -------------------------------- + + +@pytest.mark.parametrize("version", [V1, V4, D]) +def test_envelope_request_emits_verbatim(version: str) -> None: + """JSON-RPC envelope frames are identical in every protocol version; a + bodyless request emits without a params key (spec-mandated).""" + frame = JSONRPCRequest(jsonrpc="2.0", id=1, method="ping") + assert serialize_for(frame, version) == {"jsonrpc": "2.0", "id": 1, "method": "ping"} + + +def test_envelope_notification_emits_verbatim() -> None: + frame = JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized") + assert serialize_for(frame, V1) == {"jsonrpc": "2.0", "method": "notifications/initialized"} + + +def test_envelope_error_frame_emits_verbatim() -> None: + """Error frames carry id and the error object unchanged (spec-mandated).""" + frame = JSONRPCError(jsonrpc="2.0", id=5, error=ErrorData(code=-32601, message="Method not found")) + assert serialize_for(frame, V1) == { + "jsonrpc": "2.0", + "id": 5, + "error": {"code": -32601, "message": "Method not found"}, + } + + +def test_generic_envelope_interiors_are_opaque() -> None: + """The untyped result interior of a generic envelope passes through with + no injection and no strip — payload shaping applies only to typed payload + models, never by guessing what an untyped dict holds.""" + frame = JSONRPCResponse(jsonrpc="2.0", id=2, result={"resultType": "complete", "ttlMs": 9}) + assert serialize_for(frame, V1)["result"] == {"resultType": "complete", "ttlMs": 9} + + +# --- identity: released-version dumps are byte-identical ------------------ + +_IDENTITY_CASES: list[BaseModel] = [ + PingRequest(), + InitializeRequest( + params=InitializeRequestParams( + protocol_version=V2, + capabilities=ClientCapabilities(roots=RootsCapability(list_changed=True)), + client_info=Implementation(name="example-client", version="1.0.0"), + ) + ), + # A request whose params._meta carries user-set reserved keys and a vendor + # key: _meta entries are retained verbatim on emission at every version + # (deployed-peer-mandated: open _meta maps in all deployed SDKs). + CallToolRequest( + params=CallToolRequestParams( + name="echo", + arguments={"text": "hi"}, + _meta={ + PROTOCOL_VERSION_META_KEY: D, + CLIENT_INFO_META_KEY: {"name": "example-client", "version": "1.0.0"}, + CLIENT_CAPABILITIES_META_KEY: {}, + LOG_LEVEL_META_KEY: "info", + "vendor-trace": "trace-9001", + }, + ) + ), + SubscribeRequest(params=SubscribeRequestParams(uri="file:///r")), + ListRootsRequest(), + CreateMessageRequest( + params=CreateMessageRequestParams( + messages=[SamplingMessage(role="user", content=TextContent(text="q"))], max_tokens=10 + ) + ), + InitializedNotification(), + CancelledNotification(params=CancelledNotificationParams(request_id=7)), + ProgressNotification(params=ProgressNotificationParams(progress_token="t", progress=0.5)), + LoggingMessageNotification(params=LoggingMessageNotificationParams(level="info", data="x")), + EmptyResult(), + CallToolResult(content=[TextContent(text="hello")]), + ListToolsResult(tools=[Tool(name="t", input_schema={"type": "object"})]), + GetPromptResult(messages=[PromptMessage(role="user", content=TextContent(text="hi"))]), + CompleteResult(completion=Completion(values=["a"])), + ListRootsResult(roots=[Root(uri=FileUrl("file:///workspace"))]), + CreateMessageResult(role="assistant", content=TextContent(text="ok"), model="m"), +] + + +@pytest.mark.parametrize("version", RELEASED) +@pytest.mark.parametrize("model", _IDENTITY_CASES, ids=lambda model: type(model).__name__) +def test_released_version_emission_is_byte_identical(model: BaseModel, version: str) -> None: + """For values valid at the target released version, emission is the plain + monolith dump byte for byte — same keys, same order, same values.""" + assert as_bytes(serialize_for(model, version)) == as_bytes(monolith_dump(model)) + + +# --- resultType ------------------------------------------------------------ + + +def test_result_type_injected_on_2026_07_28_emission() -> None: + """resultType is required on 2026-07-28 results; an unset field emits as + "complete" (spec-mandated).""" + out = serialize_for(CallToolResult(content=[TextContent(text="hello")]), D) + assert out == {"content": [{"type": "text", "text": "hello"}], "isError": False, "resultType": "complete"} + + +def test_result_type_user_value_never_clobbered() -> None: + out = serialize_for(CallToolResult(content=[], result_type="complete"), D) + assert out["resultType"] == "complete" + + +def test_input_required_result_announces_itself() -> None: + """An input-required result emits resultType "input_required" with its + embedded requests intact (spec-mandated).""" + result = InputRequiredResult(request_state="opaque-state") + out = serialize_for(result, D) + assert out == {"requestState": "opaque-state", "resultType": "input_required"} + + +def test_result_type_stripped_below_2026_07_28() -> None: + """Even a user-set resultType is dropped on earlier versions: deployed + peers reject an empty result carrying any extra key, and retention + without the strip is exactly that failure (deployed-peer-mandated).""" + with_field = CallToolResult(content=[TextContent(text="hello")], result_type="complete") + without_field = CallToolResult(content=[TextContent(text="hello")]) + assert as_bytes(serialize_for(with_field, V4)) == as_bytes(monolith_dump(without_field)) + + +@pytest.mark.parametrize("version", [V1, V4]) +def test_empty_result_dumps_exactly_empty(version: str) -> None: + """An empty result is exactly {} on released versions — deployed peers + hard-reject any extra key there (deployed-peer-mandated).""" + assert as_bytes(serialize_for(EmptyResult(), version)) == "{}" + + +def test_empty_result_carries_result_type_at_2026_07_28() -> None: + assert serialize_for(EmptyResult(), D) == {"resultType": "complete"} + + +# --- caching directives ---------------------------------------------------- + + +def test_caching_defaults_injected_on_2026_07_28() -> None: + """ttlMs/cacheScope are required on cacheable results from 2026-07-28; + unset fields get the don't-cache pair (spec-mandated requiredness, SDK + default choice).""" + out = serialize_for(ListToolsResult(tools=[]), D) + assert out["ttlMs"] == 0 + assert out["cacheScope"] == "private" + + +def test_caching_user_values_pass_unclobbered() -> None: + out = serialize_for(ListToolsResult(tools=[], ttl_ms=5000, cache_scope="public"), D) + assert out["ttlMs"] == 5000 + assert out["cacheScope"] == "public" + + +def test_caching_fields_stripped_below_2026_07_28() -> None: + """User-set caching directives are dropped on versions that predate them; + the rest of the body is byte-identical (spec: the fields do not exist in + earlier schemas).""" + with_fields = ListToolsResult(tools=[], ttl_ms=5000, cache_scope="public") + without_fields = ListToolsResult(tools=[]) + assert as_bytes(serialize_for(with_fields, V4)) == as_bytes(monolith_dump(without_fields)) + + +def test_discover_result_emits_with_policy_defaults_at_2026_07_28() -> None: + result = DiscoverResult( + supported_versions=[V4, D], + capabilities=ServerCapabilities(), + server_info=Implementation(name="fixture-server", version="1.0.0"), + ) + out = serialize_for(result, D) + assert out["supportedVersions"] == [V4, D] + assert out["ttlMs"] == 0 + assert out["cacheScope"] == "private" + assert out["resultType"] == "complete" + assert "instructions" not in out + + +def test_discover_result_has_no_wire_form_on_released_versions() -> None: + """server/discover and its result exist only in the 2026-07-28 schema.""" + result = DiscoverResult( + supported_versions=[D], capabilities=ServerCapabilities(), server_info=Implementation(name="s", version="1") + ) + with pytest.raises(UnsupportedAtVersionError): + serialize_for(result, V4) + + +# --- reserved _meta entries on client requests ---------------------------- + + +def test_protocol_version_injected_into_request_meta_at_2026_07_28() -> None: + """2026-07-28 client requests carry the reserved _meta entries; the + boundary derives and merges protocolVersion, materializing params and + _meta when the handler left them unset (spec-mandated).""" + request = CallToolRequest( + params=CallToolRequestParams(name="get-weather", arguments={"city": "Berlin"}, _meta=as_meta(identity_meta())) + ) + out = serialize_for(request, D) + assert out["params"]["_meta"][PROTOCOL_VERSION_META_KEY] == D + assert out["params"]["_meta"][CLIENT_INFO_META_KEY] == {"name": "example-client", "version": "1.0.0"} + assert out["params"]["_meta"][CLIENT_CAPABILITIES_META_KEY] == {} + assert out["params"]["name"] == "get-weather" + assert out["params"]["arguments"] == {"city": "Berlin"} + + +def test_protocol_version_merge_never_overwrites_a_caller_value() -> None: + meta = identity_meta() | {PROTOCOL_VERSION_META_KEY: "caller-pinned"} + request = ListToolsRequest(params=PaginatedRequestParams(_meta=as_meta(meta))) + out = serialize_for(request, D) + assert out["params"]["_meta"][PROTOCOL_VERSION_META_KEY] == "caller-pinned" + + +def test_progress_token_coexists_with_the_reserved_entries() -> None: + meta = identity_meta() | {"progressToken": "tok-1"} + out = serialize_for(ListToolsRequest(params=PaginatedRequestParams(_meta=as_meta(meta))), D) + assert out["params"]["_meta"]["progressToken"] == "tok-1" + assert out["params"]["_meta"][PROTOCOL_VERSION_META_KEY] == D + + +def test_missing_session_identity_refuses_at_2026_07_28() -> None: + """The boundary never synthesizes clientInfo/clientCapabilities — they + are session identity. A bare request has no legal 2026-07-28 wire form + (the schema requires all three reserved entries).""" + with pytest.raises(UnsupportedAtVersionError) as exc_info: + serialize_for(ListToolsRequest(), D) + assert exc_info.value.version == D + assert isinstance(exc_info.value.__cause__, ValidationError) + # Two entries are missing; the message carries one and counts the rest. + assert "more" in str(exc_info.value) + + +def test_partially_missing_session_identity_also_refuses() -> None: + request = ListToolsRequest( + params=PaginatedRequestParams(_meta={CLIENT_INFO_META_KEY: {"name": "c", "version": "1"}}) + ) + with pytest.raises(UnsupportedAtVersionError) as exc_info: + serialize_for(request, D) + assert "clientCapabilities" in str(exc_info.value) + + +def test_nothing_injected_below_2026_07_28() -> None: + """On earlier versions an unset params stays omitted — the dump is the + plain monolith dump (deployed-peer-mandated byte identity).""" + assert as_bytes(serialize_for(ListToolsRequest(), V4)) == as_bytes(monolith_dump(ListToolsRequest())) + + +# --- capabilities ---------------------------------------------------------- + + +def test_roots_capability_emits_empty_at_2026_07_28() -> None: + """2026-07-28 removed roots.listChanged; the capability itself survives + and emits as the empty object (spec-mandated).""" + request = ListToolsRequest( + params=PaginatedRequestParams( + _meta={ + CLIENT_INFO_META_KEY: Implementation(name="ExampleClient", version="1.0.0"), + CLIENT_CAPABILITIES_META_KEY: ClientCapabilities(roots=RootsCapability(list_changed=True)), + } + ) + ) + out = serialize_for(request, D) + assert out == { + "method": "tools/list", + "params": { + "_meta": { + CLIENT_INFO_META_KEY: {"name": "ExampleClient", "version": "1.0.0"}, + CLIENT_CAPABILITIES_META_KEY: {"roots": {}}, + PROTOCOL_VERSION_META_KEY: D, + } + }, + } + + +def test_capabilities_extensions_stripped_below_2026_07_28() -> None: + """The extensions field is new in 2026-07-28 and must not leak by default + on earlier versions; sibling capability keys are untouched.""" + + def initialize(extensions: dict[str, Any] | None) -> InitializeRequest: + capabilities = ClientCapabilities(roots=RootsCapability(list_changed=True), extensions=extensions) + return InitializeRequest( + params=InitializeRequestParams( + protocol_version=V4, capabilities=capabilities, client_info=Implementation(name="c", version="1") + ) + ) + + with_extensions = initialize({"io.modelcontextprotocol/oauth-client-credentials": {}}) + assert as_bytes(serialize_for(with_extensions, V4)) == as_bytes(monolith_dump(initialize(None))) + + +def test_capabilities_extensions_emitted_at_2026_07_28() -> None: + """Client extensions ride the _meta clientCapabilities projection at + 2026-07-28 (spec-mandated: the field exists there).""" + meta = { + CLIENT_INFO_META_KEY: {"name": "c", "version": "1"}, + CLIENT_CAPABILITIES_META_KEY: ClientCapabilities(extensions={"io.modelcontextprotocol/x": {}}), + } + out = serialize_for(ListToolsRequest(params=PaginatedRequestParams(_meta=as_meta(meta))), D) + assert out["params"]["_meta"][CLIENT_CAPABILITIES_META_KEY] == {"extensions": {"io.modelcontextprotocol/x": {}}} + + +def test_server_extensions_emitted_in_discover_result() -> None: + result = DiscoverResult( + supported_versions=[D], + capabilities=ServerCapabilities(extensions={"io.modelcontextprotocol/y": {}}), + server_info=Implementation(name="s", version="1"), + ) + assert serialize_for(result, D)["capabilities"] == {"extensions": {"io.modelcontextprotocol/y": {}}} + + +def test_capability_extension_values_admit_every_json_type_at_2026_07_28() -> None: + """Extension values are arbitrary JSON, so fractional numbers and nulls at + any depth survive the 2026-07-28 revalidation (spec-mandated: the schema + source types extension values as any JSON value).""" + extension_value = {"ratio": 0.5, "experimental": None, "steps": [1.5, None, "done"]} + meta = { + CLIENT_INFO_META_KEY: {"name": "c", "version": "1"}, + CLIENT_CAPABILITIES_META_KEY: ClientCapabilities(extensions={"io.modelcontextprotocol/x": extension_value}), + } + out = serialize_for(ListToolsRequest(params=PaginatedRequestParams(_meta=as_meta(meta))), D) + emitted = out["params"]["_meta"][CLIENT_CAPABILITIES_META_KEY]["extensions"]["io.modelcontextprotocol/x"] + assert emitted == extension_value + + +# --- tasks (2025-11-25 only) ------------------------------------------------ + + +def test_task_metadata_emitted_at_2025_11_25() -> None: + """The task field on augmentable params exists only in the 2025-11-25 + schema (spec-mandated).""" + request = CallToolRequest(params=CallToolRequestParams(name="t", task=TaskMetadata(ttl=60_000))) + assert serialize_for(request, V4)["params"]["task"] == {"ttl": 60000} + + +@pytest.mark.parametrize("version", [V3, D]) +def test_task_metadata_stripped_outside_2025_11_25(version: str) -> None: + meta = as_meta(identity_meta()) if version == D else None + + def request(task: TaskMetadata | None) -> CallToolRequest: + return CallToolRequest(params=CallToolRequestParams(name="t", task=task, _meta=meta)) + + out = serialize_for(request(TaskMetadata(ttl=60_000)), version) + assert "task" not in out["params"] + if version == V3: + assert as_bytes(out) == as_bytes(monolith_dump(request(None))) + + +def _initialize_with_tasks(protocol_version: str, tasks: dict[str, Any] | None) -> InitializeRequest: + capabilities = ClientCapabilities() if tasks is None else ClientCapabilities.model_validate({"tasks": tasks}) + return InitializeRequest( + params=InitializeRequestParams( + protocol_version=protocol_version, + capabilities=capabilities, + client_info=Implementation(name="c", version="1"), + ) + ) + + +def test_tasks_capability_subtree_emitted_at_2025_11_25() -> None: + request = _initialize_with_tasks(V4, {"requests": {"sampling": {"createMessage": {}}}}) + out = serialize_for(request, V4) + assert out["params"]["capabilities"]["tasks"] == {"requests": {"sampling": {"createMessage": {}}}} + + +def test_tasks_capability_subtree_stripped_below_2025_11_25() -> None: + with_tasks = _initialize_with_tasks(V3, {"requests": {"sampling": {"createMessage": {}}}}) + without_tasks = _initialize_with_tasks(V3, None) + assert as_bytes(serialize_for(with_tasks, V3)) == as_bytes(monolith_dump(without_tasks)) + + +# --- newer optional fields pass through (no narrowing) --------------------- + + +def test_icons_and_title_pass_through_on_older_versions() -> None: + """New optional fields on known types are wire-safe against every + deployed peer; emission never strips them on versions that predate them + (deployed-peer-mandated: no gating needed).""" + result = ListToolsResult( + tools=[Tool(name="t", title="T", input_schema={"type": "object"}, icons=[Icon(src="https://e/i.png")])] + ) + for version in (V1, V3): + out = serialize_for(result, version) + assert out["tools"][0]["title"] == "T" + assert out["tools"][0]["icons"] == [{"src": "https://e/i.png"}] + assert as_bytes(out) == as_bytes(monolith_dump(result)) + + +@pytest.mark.parametrize("version", [V1, V3, D]) +def test_scalar_structured_content_passes_at_every_version(version: str) -> None: + """Values are never narrowed on emission: a non-object structuredContent + passes through unchanged everywhere.""" + out = serialize_for(CallToolResult(content=[], structured_content=5), version) + assert out["structuredContent"] == 5 + + +def test_unset_structured_content_is_absent() -> None: + out = serialize_for(CallToolResult(content=[]), D) + assert "structuredContent" not in out + + +def test_object_structured_content_emitted_at_2026_07_28() -> None: + out = serialize_for( + CallToolResult(content=[TextContent(text="22.5 C")], structured_content={"temperature": 22.5}), D + ) + assert out["structuredContent"] == {"temperature": 22.5} + assert out["resultType"] == "complete" + + +def test_opened_tool_schemas_pass_through_unchanged() -> None: + """Tool input schemas accept the full JSON Schema vocabulary; every + keyword — including $ref/$defs and conditionals — survives emission + verbatim (spec-mandated: the schemas leave these objects open).""" + schema = { + "type": "object", + "properties": {"query": {"$ref": "#/$defs/nonEmptyString"}}, + "required": ["query"], + "if": {"required": ["mode"]}, + "then": {"required": ["filters"]}, + "$defs": {"nonEmptyString": {"type": "string", "minLength": 1}}, + } + result = ListToolsResult(tools=[Tool(name="search", input_schema=schema)], ttl_ms=0, cache_scope="private") + assert serialize_for(result, D)["tools"][0]["inputSchema"] == schema + + +# --- content blocks --------------------------------------------------------- + + +def test_audio_content_passes_through_at_2024_11_05() -> None: + """audio content entered the schema in 2025-03-26 but is deliberately not + gated on emission to older peers (sibling parity; peers reject unknown + blocks at request level — accepted risk).""" + result = CallToolResult(content=[AudioContent(data="QQ==", mime_type="audio/wav")]) + out = serialize_for(result, V1) + assert out["content"][0] == {"type": "audio", "data": "QQ==", "mimeType": "audio/wav"} + + +@pytest.mark.parametrize("version", [V1, V2]) +def test_resource_link_passes_through_before_2025_06_18(version: str) -> None: + result = CallToolResult(content=[ResourceLink(name="r", uri="https://example.com/r")]) + out = serialize_for(result, version) + assert out["content"][0]["type"] == "resource_link" + assert out["content"][0]["uri"] == "https://example.com/r" + + +# --- sampling and tool content bounds --------------------------------------- + + +def test_tool_content_refused_at_2025_06_18_and_earlier() -> None: + """tool_use/tool_result sampling content entered the schema in + 2025-11-25; earlier revisions have no representation for it, and dropping + it would change meaning (spec-mandated single-block content there).""" + request = CreateMessageRequest( + params=CreateMessageRequestParams( + messages=[SamplingMessage(role="user", content=ToolUseContent(name="t", id="1", input={}))], + max_tokens=5, + ) + ) + with pytest.raises(UnsupportedAtVersionError): + serialize_for(request, V3) + assert serialize_for(request, V4)["params"]["messages"][0]["content"]["type"] == "tool_use" + + +def test_array_sampling_content_refused_at_2025_06_18_and_earlier() -> None: + """Multi-block sampling messages have no lossless collapse to the + single-block shape of 2025-06-18 and earlier (spec-mandated).""" + request = CreateMessageRequest( + params=CreateMessageRequestParams( + messages=[SamplingMessage(role="user", content=[TextContent(text="a"), TextContent(text="b")])], + max_tokens=5, + ) + ) + with pytest.raises(UnsupportedAtVersionError): + serialize_for(request, V3) + out = serialize_for(request, V4) + assert out["params"]["messages"][0]["content"] == [ + {"type": "text", "text": "a"}, + {"type": "text", "text": "b"}, + ] + + +def test_wide_sampling_result_refused_at_2025_06_18_and_earlier() -> None: + """The wide-content sampling result is typed wide by the schemas from + 2025-11-25; through 2025-06-18 the same wire class is single-block, so + the wide SDK class has no legal form there (spec-mandated).""" + result = CreateMessageResultWithTools(role="assistant", content=[TextContent(text="x")], model="m") + with pytest.raises(UnsupportedAtVersionError): + serialize_for(result, V3) + assert serialize_for(result, V4)["content"] == [{"type": "text", "text": "x"}] + + +# --- multi-round-trip results ------------------------------------------------ + + +def test_input_required_result_refused_below_2026_07_28() -> None: + """InputRequiredResult exists only in the 2026-07-28 schema; on earlier + versions there is no type to validate against (spec-mandated).""" + result = InputRequiredResult(request_state="s") + with pytest.raises(UnsupportedAtVersionError) as exc_info: + serialize_for(result, V4) + assert exc_info.value.version == V4 + + +def test_empty_input_required_result_refused() -> None: + """The 2026-07-28 schema requires at least one of inputRequests / + requestState on the wire; the constraint is spec prose, checked + explicitly (spec-mandated).""" + with pytest.raises(UnsupportedAtVersionError, match="neither input_requests nor request_state"): + serialize_for(InputRequiredResult(), D) + + +def test_embedded_input_responses_pass_through_verbatim() -> None: + """The boundary never reshapes embedded request/response payloads: + caller-set _meta and resultType on inputResponses values survive + 2026-07-28 emission untouched (embedded hygiene is the caller's job).""" + embedded = CreateMessageResult( + role="assistant", content=TextContent(text="ok"), model="m", result_type="complete", _meta={"k": "v"} + ) + request = CallToolRequest( + params=CallToolRequestParams(name="retry-me", _meta=as_meta(identity_meta()), input_responses={"r1": embedded}) + ) + out = serialize_for(request, D) + entry = out["params"]["inputResponses"]["r1"] + assert entry["resultType"] == "complete" + assert entry["_meta"] == {"k": "v"} + + +# --- elicitation and cancellation bounds ------------------------------------- + + +def test_url_mode_elicitation_refused_at_2025_06_18() -> None: + """URL-mode elicitation entered the schema in 2025-11-25 (spec-mandated + version floor).""" + request = ElicitRequest( + params=ElicitRequestURLParams(message="auth needed", url="https://example.com/auth", elicitation_id="e-1") + ) + with pytest.raises(UnsupportedAtVersionError): + serialize_for(request, V3) + assert serialize_for(request, V4)["params"]["mode"] == "url" + + +def test_list_string_elicit_content_refused_below_2025_11_25() -> None: + """Multi-select (list-of-strings) elicitation values entered the schema + in 2025-11-25 (spec-mandated).""" + result = ElicitResult(action="accept", content={"choices": ["a", "b"]}) + with pytest.raises(UnsupportedAtVersionError): + serialize_for(result, V3) + assert serialize_for(result, V4)["content"] == {"choices": ["a", "b"]} + + +@pytest.mark.parametrize("version", [V3, V4, D]) +def test_form_elicitation_schema_bounds_emit_byte_identically(version: str) -> None: + """The requested-schema interior is caller data and travels verbatim: + re-validation through a version package decides only which keys survive, + never the values — so a fractional bound keeps its exact JSON rendering + (1.0 stays 1.0) and an integral one is never re-rendered (120 stays 120), + whatever numeric kind the target version's package declares for the field + (deployed-peer-mandated byte identity).""" + request = ElicitRequest( + params=ElicitRequestFormParams( + message="How old are you?", + requested_schema={ + "type": "object", + "properties": {"age": {"type": "number", "minimum": 1.0, "maximum": 120}}, + }, + ) + ) + assert as_bytes(serialize_for(request, version)) == as_bytes(monolith_dump(request)) + + +@pytest.mark.parametrize("version", [V3, V4, D]) +def test_fractional_elicit_content_emits_at_every_modeled_version(version: str) -> None: + """Form answers are string | number | boolean (string arrays from + 2025-11-25), so a fractional number is a legal elicitation answer at + every version that models elicitation; the value keeps its exact JSON + rendering (spec-mandated; the pinned schema renderings say "integer" + only as a render artifact the version modules deliberately widen).""" + result = ElicitResult(action="accept", content={"ratio": 0.5}) + out = serialize_for(result, version) + assert out["content"] == {"ratio": 0.5} + if version != D: # at 2026-07-28 the injected resultType is the only delta + assert as_bytes(out) == as_bytes(monolith_dump(result)) + + +@pytest.mark.parametrize("version", [V3, V4, D]) +def test_null_elicit_content_values_pass_through_at_every_modeled_version(version: str) -> None: + """No schema version types a null elicitation answer — the monolith's + None value arm exists for v1.x constructor compatibility — but emitted + values are caller data and travel verbatim at every version that models + elicitation, exactly as python v1.x itself constructs, accepts, and + emits the same body (deployed-peer-mandated pass-through; a version + package's narrower value typing decides parses, never emissions).""" + result = ElicitResult(action="accept", content={"x": None, "y": "ok"}) + out = serialize_for(result, version) + assert out["content"] == {"x": None, "y": "ok"} + if version != D: # at 2026-07-28 the injected resultType is the only delta + assert as_bytes(out) == as_bytes(monolith_dump(result)) + + +def test_elicit_result_has_no_wire_form_before_2025_06_18() -> None: + """Elicitation entered the schema in 2025-06-18 (spec-mandated version + floor); no value, fractional or not, has an earlier wire form.""" + with pytest.raises(UnsupportedAtVersionError): + serialize_for(ElicitResult(action="accept", content={"ratio": 0.5}), V2) + + +@pytest.mark.parametrize("version", [V3, V4]) +def test_fractional_schema_bounds_emit_byte_identically(version: str) -> None: + """JSON Schema number bounds are numbers: a fractional minimum/maximum in + a requested schema is legal at every version with elicitation and emits + byte-identically (spec-mandated; integer-only bounds in the pinned + schema renderings are the same render artifact).""" + request = ElicitRequest( + params=ElicitRequestFormParams( + message="Rate this answer", + requested_schema={ + "type": "object", + "properties": {"score": {"type": "number", "minimum": 0.5, "maximum": 9.5}}, + }, + ) + ) + assert as_bytes(serialize_for(request, version)) == as_bytes(monolith_dump(request)) + + +def test_cancelled_notification_requires_request_id_through_2025_06_18() -> None: + """requestId on a cancellation is required through 2025-06-18 and + optional from 2025-11-25 (spec-mandated).""" + without_id = CancelledNotification(params=CancelledNotificationParams(reason="bored")) + with pytest.raises(UnsupportedAtVersionError) as exc_info: + serialize_for(without_id, V3) + assert "more" not in str(exc_info.value) # exactly one underlying error + assert serialize_for(without_id, V4) == {"method": "notifications/cancelled", "params": {"reason": "bored"}} + with_id = CancelledNotification(params=CancelledNotificationParams(request_id=7)) + assert serialize_for(with_id, V3) == {"method": "notifications/cancelled", "params": {"requestId": 7}} + + +# --- subscriptions ----------------------------------------------------------- + + +def test_subscription_filter_extras_survive_emission() -> None: + """Extensions merge extra keys into the subscription filter on the wire; + they survive 2026-07-28 emission (spec-mandated open object).""" + filter_ = SubscriptionFilter.model_validate({"toolsListChanged": True, "taskIds": ["task-1"]}) + request = SubscriptionsListenRequest( + params=SubscriptionsListenRequestParams(notifications=filter_, _meta=as_meta(identity_meta())) + ) + out = serialize_for(request, D) + assert out["params"]["notifications"] == {"toolsListChanged": True, "taskIds": ["task-1"]} + + +def test_legacy_subscribe_has_no_wire_form_at_2026_07_28() -> None: + """2026-07-28 removed resources/subscribe (spec-mandated).""" + with pytest.raises(UnsupportedAtVersionError): + serialize_for(SubscribeRequest(params=SubscribeRequestParams(uri="file:///r")), D) + + +def test_listen_request_has_no_wire_form_below_2026_07_28() -> None: + request = SubscriptionsListenRequest(params=SubscriptionsListenRequestParams(notifications=SubscriptionFilter())) + with pytest.raises(UnsupportedAtVersionError): + serialize_for(request, V4) + + +def test_initialize_has_no_wire_form_at_2026_07_28() -> None: + """2026-07-28 removed the initialize handshake; server/discover replaces + it (spec-mandated).""" + request = InitializeRequest( + params=InitializeRequestParams( + protocol_version=V4, capabilities=ClientCapabilities(), client_info=Implementation(name="c", version="1") + ) + ) + with pytest.raises(UnsupportedAtVersionError): + serialize_for(request, D) + + +def test_ping_has_no_wire_form_at_2026_07_28() -> None: + with pytest.raises(UnsupportedAtVersionError): + serialize_for(PingRequest(), D) + + +# --- spec-name divergences ---------------------------------------------------- + + +def test_elicit_complete_notification_emits_under_its_schema_name() -> None: + """The SDK keeps its v1 class name; the schema spells the definition + 'ElicitationCompleteNotification'. Emission resolves through the recorded + rename and the wire shape is unchanged.""" + notification = ElicitCompleteNotification(params=ElicitCompleteNotificationParams(elicitation_id="e-1")) + assert serialize_for(notification, V4) == { + "method": "notifications/elicitation/complete", + "params": {"elicitationId": "e-1"}, + } + + +# --- alignment defect guard ----------------------------------------------------- + + +def test_an_invented_redump_key_raises() -> None: + """A re-validated model emitting a key the original dump never had is + always a defect in a version package; the alignment walk refuses to emit + it. Unreachable through the committed packages, so pinned directly.""" + with pytest.raises(RuntimeError, match="invented output keys"): + _merge_and_align({"a": 1}, {"a": 1, "b": 2}) diff --git a/tests/types/test_wire_parse.py b/tests/types/test_wire_parse.py new file mode 100644 index 0000000000..3798fd4904 --- /dev/null +++ b/tests/types/test_wire_parse.py @@ -0,0 +1,544 @@ +"""Inbound parsing facts of the wire boundary. + +Parsing is one lenient superset parse at every version, plus the 2026-07-28 +mandates (an unrecognized resultType value, the reserved request _meta +entries — each with a pinned error type string — and the required method on +embedded input-request entries), structural member selection for +result-bearing unions, and the unknown-content-tag refinement. +Each test names the spec fact in plain words with its provenance class +(spec-mandated vs deployed-peer-mandated). +""" + +from __future__ import annotations + +from typing import Any + +import pytest +from pydantic import ValidationError + +from mcp.types import ( + CLIENT_CAPABILITIES_META_KEY, + CLIENT_INFO_META_KEY, + PROTOCOL_VERSION_META_KEY, + AudioContent, + CallToolRequest, + CallToolResult, + CancelledNotification, + ClientNotification, + ClientRequest, + ClientResult, + CompleteResult, + ContentBlock, + CreateMessageResult, + CreateMessageResultWithTools, + DiscoverResult, + EmptyResult, + GetPromptResult, + InitializeResult, + InputRequiredResult, + JSONRPCRequest, + ListPromptsResult, + ListResourcesResult, + ListResourceTemplatesResult, + ListRootsRequest, + ListToolsResult, + ReadResourceResult, + ServerRequest, + ServerResult, + ToolUseContent, + server_result_adapter, +) +from mcp.types.jsonrpc import JSONRPCMessage +from mcp.types.wire import parse_as + +V1 = "2024-11-05" +V4 = "2025-11-25" +D = "2026-07-28" + + +def error_types(exc_info: pytest.ExceptionInfo[ValidationError]) -> set[str]: + return {error["type"] for error in exc_info.value.errors()} + + +def triple_meta() -> dict[str, Any]: + return { + PROTOCOL_VERSION_META_KEY: D, + CLIENT_INFO_META_KEY: {"name": "c", "version": "1"}, + CLIENT_CAPABILITIES_META_KEY: {}, + } + + +# --- lenient superset parse --------------------------------------------------- + + +@pytest.mark.parametrize("version", [V1, V4, D]) +def test_unknown_fields_never_reject(version: str) -> None: + """Unknown fields are ignored at every version, never rejected + (deployed-peer-mandated: inbound strictness is the recorded interop + failure mode).""" + result = parse_as(CallToolResult, {"content": [], "futureField": {"x": 1}}, version) + assert result == CallToolResult(content=[]) + + +def test_audio_content_parses_even_at_2024_11_05() -> None: + """Inbound membership is the superset at every version: audio content + parses on a 2024-11-05 session even though that schema predates it.""" + block = parse_as(ContentBlock, {"type": "audio", "data": "QQ==", "mimeType": "audio/wav"}, V1) + assert isinstance(block, AudioContent) + + +def test_future_protocol_version_value_accepted() -> None: + """A future or unknown protocolVersion VALUE inside initialize params is + a plain string — version acceptability is negotiation logic, not parsing + (spec-mandated shape).""" + request = parse_as( + ClientRequest, + { + "method": "initialize", + "params": { + "protocolVersion": "2099-12-31", + "capabilities": {}, + "clientInfo": {"name": "c", "version": "1"}, + }, + }, + V4, + ) + assert request.params.protocol_version == "2099-12-31" + + +def test_unknown_version_string_parses_with_no_mandates() -> None: + """An unknown version is most plausibly newer than this SDK: the parse + stays lenient and no version-keyed mandate applies, with no exception for + the version string itself.""" + result = parse_as(ServerResult, {"resultType": "finished"}, "2099-01-01") + assert isinstance(result, EmptyResult) + assert result.result_type == "finished" + + +# --- retention ------------------------------------------------------------------ + + +@pytest.mark.parametrize("version", [V4, D]) +def test_unknown_meta_keys_are_retained(version: str) -> None: + """Unknown _meta keys are retained verbatim in both directions at every + version (deployed-peer-mandated: open _meta maps everywhere).""" + meta: dict[str, Any] = {"vendor-trace": "trace-9001"} + if version == D: + meta.update(triple_meta()) + payload: dict[str, Any] = {"method": "tools/call", "params": {"name": "echo", "_meta": meta}} + request = parse_as(ClientRequest, payload, version) + assert isinstance(request, CallToolRequest) + assert request.params.meta is not None + assert request.params.meta["vendor-trace"] == "trace-9001" + + +def test_caching_fields_retained_on_parse_at_any_version() -> None: + """ttlMs/cacheScope are optional and retained on parse at every version + (spec-mandated only at 2026-07-28; leniency elsewhere).""" + result = parse_as(ListToolsResult, {"tools": [], "ttlMs": 1000, "cacheScope": "public"}, V1) + assert result.ttl_ms == 1000 + assert result.cache_scope == "public" + + +def test_task_metadata_surfaced_on_2025_11_25_parse() -> None: + request = parse_as(ClientRequest, {"method": "tools/call", "params": {"name": "t", "task": {"ttl": 5}}}, V4) + assert isinstance(request, CallToolRequest) + assert request.params.task is not None + assert request.params.task.ttl == 5 + + +# --- resultType mandate (2026-07-28) --------------------------------------------- + + +def test_unrecognized_result_type_rejects_at_2026_07_28() -> None: + """resultType discriminates result handling from 2026-07-28; a value the + revision does not define must reject so the client never misreads a + result kind (spec-mandated). Pinned error type: result_type_invalid.""" + with pytest.raises(ValidationError) as exc_info: + parse_as(ServerResult, {"resultType": "finished"}, D) + assert error_types(exc_info) == {"result_type_invalid"} + + +def test_unrecognized_result_type_on_concrete_result_also_rejects() -> None: + with pytest.raises(ValidationError) as exc_info: + parse_as(CallToolResult, {"content": [], "resultType": "finished"}, D) + assert error_types(exc_info) == {"result_type_invalid"} + + +def test_absent_result_type_accepted_with_no_materialization() -> None: + """Absent means "complete"; the parsed field stays None (spec-mandated).""" + result = parse_as(CallToolResult, {"content": []}, D) + assert result.result_type is None + + +def test_recognized_result_type_retained() -> None: + result = parse_as(CallToolResult, {"content": [], "resultType": "complete"}, D) + assert result.result_type == "complete" + + +def test_any_result_type_accepted_below_2026_07_28() -> None: + """Earlier versions never reject the field — strictly-more-lenient parse + (sanctioned leniency flip).""" + result = parse_as(ServerResult, {"resultType": "finished"}, V4) + assert isinstance(result, EmptyResult) + assert result.result_type == "finished" + + +def test_stray_result_type_on_a_request_stays_accepted() -> None: + """The mandate is scoped to results: on a request the key is an ordinary + unknown field (never rejected).""" + payload = {"method": "tools/call", "params": {"name": "t", "_meta": triple_meta()}, "resultType": "bogus"} + request = parse_as(ClientRequest, payload, D) + assert isinstance(request, CallToolRequest) + + +# --- reserved _meta mandate (2026-07-28, server side) ------------------------------ + + +def test_missing_meta_container_rejects_at_2026_07_28() -> None: + """Every 2026-07-28 client request carries the three reserved _meta + entries (spec-mandated). Pinned error type: missing_required_meta.""" + with pytest.raises(ValidationError) as exc_info: + parse_as(ClientRequest, {"method": "tools/list"}, D) + assert error_types(exc_info) == {"missing_required_meta"} + assert len(exc_info.value.errors()) == 3 # each entry independently required + + +def test_partial_triple_rejects_at_2026_07_28() -> None: + payload = { + "method": "tools/call", + "params": { + "name": "echo", + "arguments": {"text": "hi"}, + "_meta": { + PROTOCOL_VERSION_META_KEY: D, + CLIENT_INFO_META_KEY: {"name": "meta-fixture-client", "version": "1.0.0"}, + }, + }, + } + with pytest.raises(ValidationError) as exc_info: + parse_as(ClientRequest, payload, D) + assert error_types(exc_info) == {"missing_required_meta"} + (error,) = exc_info.value.errors() + assert error["loc"] == ("params", "_meta", CLIENT_CAPABILITIES_META_KEY) + + +def test_full_triple_parses() -> None: + payload = {"method": "tools/call", "params": {"name": "echo", "_meta": triple_meta()}} + request = parse_as(ClientRequest, payload, D) + assert isinstance(request, CallToolRequest) + + +def test_no_triple_needed_below_2026_07_28() -> None: + request = parse_as(ClientRequest, {"method": "tools/call", "params": {"name": "echo"}}, V4) + assert isinstance(request, CallToolRequest) + + +def test_notifications_never_need_the_triple() -> None: + """The mandate covers client requests only; notifications have no + reserved-entry requirement (spec-mandated scope).""" + notification = parse_as(ClientNotification, {"method": "notifications/cancelled", "params": {}}, D) + assert isinstance(notification, CancelledNotification) + + +def test_requests_outside_the_2026_07_28_client_set_never_need_the_triple() -> None: + """The reserved entries are required on 2026-07-28 client requests only; + a request with any other method parses without them.""" + request = parse_as(ServerRequest, {"method": "roots/list"}, D) + assert isinstance(request, ListRootsRequest) + + +# --- embedded input-request entries (2026-07-28) ------------------------------------- + + +def test_input_request_entry_without_method_rejects_structurally() -> None: + """inputRequests values are full request payloads, so method is required + on each entry; an entry with no method is a structural failure — plain + missing-field error, never an unknown union member (spec-mandated).""" + payload = { + "resultType": "input_required", + "inputRequests": {"elicit-1": {"params": {"message": "Please provide your GitHub username"}}}, + } + with pytest.raises(ValidationError) as exc_info: + parse_as(ServerResult, payload, D) + assert error_types(exc_info) == {"missing"} + (error,) = exc_info.value.errors() + assert error["loc"] == ("inputRequests", "elicit-1", "method") + + +def test_input_request_entries_with_methods_parse() -> None: + payload: dict[str, Any] = { + "resultType": "input_required", + "inputRequests": { + "elicit-1": { + "method": "elicitation/create", + "params": {"message": "username?", "mode": "form", "requestedSchema": {"type": "object"}}, + }, + "roots-1": {"method": "roots/list"}, + }, + } + result = parse_as(ServerResult, payload, D) + assert isinstance(result, InputRequiredResult) + assert result.input_requests is not None + assert set(result.input_requests) == {"elicit-1", "roots-1"} + + +# --- resultType discrimination (2026-07-28, unions only) -------------------------------- + + +def test_input_required_body_routes_to_input_required_arm() -> None: + """2026-07-28 response bodies discriminate by resultType: an + input-required body resolves to InputRequiredResult even when it carries + fields of another member (spec-mandated).""" + payload = { + "resultType": "input_required", + "content": [{"type": "text", "text": "partial"}], + "requestState": "opaque", + } + result = parse_as(ServerResult, payload, D) + assert isinstance(result, InputRequiredResult) + assert result.request_state == "opaque" + + +def test_complete_body_with_content_parses_as_call_tool_result() -> None: + payload = {"resultType": "complete", "content": [{"type": "text", "text": "done"}]} + result = parse_as(ServerResult, payload, D) + assert isinstance(result, CallToolResult) + + +def test_concrete_type_is_never_rerouted() -> None: + """parse_as(CallToolResult, ...) returns a CallToolResult or fails on its + own terms — the discrimination applies to union targets only, so the + annotated return type is honest.""" + payload: dict[str, Any] = {"resultType": "input_required", "content": []} + result = parse_as(CallToolResult, payload, D) + assert isinstance(result, CallToolResult) + assert result.result_type == "input_required" + + +def test_union_without_the_input_required_arm_is_untouched() -> None: + """The reroute needs the input-required arm in the union; a result union + without it parses structurally as usual.""" + result = parse_as(ClientResult, {"resultType": "input_required"}, D) + assert isinstance(result, EmptyResult) + + +# --- structural member selection for result unions -------------------------------------- + + +def test_discover_result_missing_supported_versions_rejects() -> None: + """supportedVersions is required on a discover result; a body carrying + the discover key set but missing it must reject as invalid params, not + quietly parse as an empty result (spec-mandated).""" + payload = { + "capabilities": {}, + "serverInfo": {"name": "probe-server", "version": "0.1.0"}, + "ttlMs": 1000, + "cacheScope": "public", + "resultType": "complete", + } + with pytest.raises(ValidationError) as exc_info: + parse_as(ServerResult, payload, D) + assert error_types(exc_info) == {"missing"} + + +def test_unknown_shaped_result_bodies_still_parse_as_empty_result() -> None: + """A body matching no member better than the base result shape parses as + the EmptyResult arm — unknown shapes are not rejected (leniency).""" + result = parse_as(ServerResult, {"someVendorKey": 1}, V4) + assert isinstance(result, EmptyResult) + + +def test_complete_discover_result_parses_via_selection() -> None: + payload = { + "supportedVersions": [V4, D], + "capabilities": {}, + "serverInfo": {"name": "s", "version": "1"}, + "ttlMs": 0, + "cacheScope": "private", + "resultType": "complete", + } + result = parse_as(ServerResult, payload, D) + assert isinstance(result, DiscoverResult) + + +# The monolith splits the schema's single sampling result into a single-content +# arm and an array-content arm with the same top-level keys (SDK-defined split); +# selection tries every structurally matching arm, best match first, so a body +# its best-looking arm rejects still parses when a sibling accepts it. + + +@pytest.mark.parametrize("version", [V1, "2025-03-26", "2025-06-18", V4, D]) +def test_sampling_with_tools_body_parses_as_the_array_content_arm(version: str) -> None: + """A sampling response whose content is an array with a tool-use block + (legal wire shape since 2025-11-25) is rejected by the single-content arm + and must fall through to the array-content arm; inbound membership is + never version-gated.""" + body: dict[str, Any] = { + "role": "assistant", + "content": [ + {"type": "text", "text": "checking the weather"}, + {"type": "tool_use", "id": "call-1", "name": "get_weather", "input": {}}, + ], + "model": "example-model", + } + result = parse_as(ClientResult, body, version) + assert isinstance(result, CreateMessageResultWithTools) + assert isinstance(result.content, list) + assert result.content[1] == ToolUseContent(id="call-1", name="get_weather", input={}) + + +def test_single_content_sampling_body_parses_as_the_single_content_arm() -> None: + """A single non-tool content block satisfies both sampling arms; the + first-declared (single-content) arm wins, so plain sampling responses keep + resolving exactly as they did before the array-content arm existed.""" + body = {"role": "assistant", "content": {"type": "text", "text": "hi"}, "model": "example-model"} + result = parse_as(ClientResult, body, V4) + assert isinstance(result, CreateMessageResult) + + +def test_result_body_rejected_with_the_best_matching_arms_errors_when_no_arm_validates() -> None: + """A body keyed like a discover result but missing required + supportedVersions matches several arms structurally and validates as none + of them; the reject surfaces the best-matching arm's own errors.""" + body: dict[str, Any] = { + "capabilities": {}, + "serverInfo": {"name": "s", "version": "1"}, + "ttlMs": 1000, + "cacheScope": "public", + } + with pytest.raises(ValidationError) as exc_info: + parse_as(ServerResult, body, D) + (error,) = exc_info.value.errors() + assert error["type"] == "missing" + assert error["loc"] == ("supportedVersions",) + + +# --- unknown content type refinement ------------------------------------------------------ + + +@pytest.mark.parametrize("version", [V1, V4, D]) +def test_unknown_content_type_rejects_at_every_version(version: str) -> None: + """The content union is closed in every deployed SDK: an unknown type tag + is an unknown union member at every version (deployed-peer-mandated). + Pinned error type: union_tag_invalid.""" + payload = {"type": "holographic", "data": "QmFzZTY0", "mimeType": "model/vnd.example-hologram"} + with pytest.raises(ValidationError) as exc_info: + parse_as(ContentBlock, payload, version) + assert error_types(exc_info) == {"union_tag_invalid"} + + +@pytest.mark.parametrize("version", [V4, D]) +def test_unknown_content_type_nested_in_a_result_rejects(version: str) -> None: + """The refinement reaches tags failing nested inside a parsed result's + content list, with the error located at the failing item.""" + payload = {"resultType": "complete", "content": [{"type": "carousel-deck", "slides": ["aGVsbG8="]}]} + with pytest.raises(ValidationError) as exc_info: + parse_as(ServerResult, payload, version) + assert error_types(exc_info) == {"union_tag_invalid"} + (error,) = exc_info.value.errors() + assert error["loc"] == ("content", 0) + + +def test_unknown_tag_among_valid_siblings_rejects() -> None: + payload: dict[str, Any] = { + "resultType": "complete", + "content": [ + {"type": "text", "text": "ok"}, + {"type": "carousel-deck", "slides": []}, + ], + } + with pytest.raises(ValidationError) as exc_info: + parse_as(ServerResult, payload, V4) + (error,) = exc_info.value.errors() + assert error["loc"] == ("content", 1) + + +def test_known_tag_with_bad_fields_keeps_structural_errors() -> None: + """A recognized tag with invalid fields is not an unknown member; the + structural errors pass through untouched.""" + with pytest.raises(ValidationError) as exc_info: + parse_as(ContentBlock, {"type": "text"}, V4) + assert "union_tag_invalid" not in error_types(exc_info) + + +def test_tag_less_text_content_parses_via_the_defaulted_tag() -> None: + """The monolith content models default their type tag, so a tag-less dict + that satisfies one member's fields parses as that member (the lenient v1 + behavior, unchanged).""" + block = parse_as(ContentBlock, {"text": "no tag at all"}, V4) + assert block == parse_as(ContentBlock, {"type": "text", "text": "no tag at all"}, V4) + + +def test_tag_less_content_failing_every_member_keeps_structural_errors() -> None: + """A tag-less dict that fits no member is a structural failure, not an + unknown union member.""" + with pytest.raises(ValidationError) as exc_info: + parse_as(ContentBlock, {}, V4) + assert "union_tag_invalid" not in error_types(exc_info) + + +def test_non_dict_content_item_keeps_structural_errors() -> None: + with pytest.raises(ValidationError) as exc_info: + parse_as(CallToolResult, {"content": [42]}, V4) + assert "union_tag_invalid" not in error_types(exc_info) + + +# --- envelope frames -------------------------------------------------------------------------- + + +def test_envelope_frame_parses_at_envelope_level_only() -> None: + """Frame parsing types the envelope; generic bodies stay untyped dicts, + so no payload mandate reaches inside them.""" + frame = parse_as(JSONRPCMessage, {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}, D) + assert isinstance(frame, JSONRPCRequest) + assert frame.params is None + + +# --- resolution pins for the public result adapter --------------------------------------------- + +# One typed frame per arm the union carried before the 2026-07-28 growth, the +# two minimal bodies, and one 2026-07-28-shaped frame. The plain smart-union +# adapter is public API; these pins freeze its resolution so growing the +# union can never silently re-route an existing frame. +_RESOLUTION_PINS: list[tuple[dict[str, Any], type[Any]]] = [ + ( + {"protocolVersion": "2025-03-26", "capabilities": {}, "serverInfo": {"name": "s", "version": "1"}}, + InitializeResult, + ), + ({"completion": {"values": []}}, CompleteResult), + ({"messages": [{"role": "user", "content": {"type": "text", "text": "hi"}}]}, GetPromptResult), + ({"prompts": []}, ListPromptsResult), + ({"resources": []}, ListResourcesResult), + ({"resourceTemplates": []}, ListResourceTemplatesResult), + ({"contents": []}, ReadResourceResult), + ({"content": [{"type": "text", "text": "hi"}]}, CallToolResult), + ({"tools": []}, ListToolsResult), + ({}, EmptyResult), + ({"_meta": {"vendor": "x"}}, EmptyResult), + # The full 2026-07-28 server/discover result key set; supportedVersions + # exists in no earlier published schema, so no released-version peer can + # produce this frame. It resolves to the discover arm and stays accepted + # — a deliberate, pinned outcome of growing the union. + ( + { + "supportedVersions": ["2025-11-25", "2026-07-28"], + "capabilities": {}, + "serverInfo": {"name": "s", "version": "1"}, + }, + DiscoverResult, + ), +] + + +@pytest.mark.parametrize( + ("payload", "expected"), + _RESOLUTION_PINS, + ids=[f"{index}-{expected.__name__}" for index, (_, expected) in enumerate(_RESOLUTION_PINS)], +) +def test_server_result_adapter_resolution_is_pinned(payload: dict[str, Any], expected: type[Any]) -> None: + """Every frame resolvable before the union grew still resolves to the + same class, and the minimal bodies stay EmptyResult — the appended + all-optional input-required arm absorbs nothing.""" + resolved = server_result_adapter.validate_python(payload) + assert type(resolved) is expected