diff --git a/docs/migration.md b/docs/migration.md index 850e052550..b03f728a11 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,10 @@ 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. + ### `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..1ff62cb61d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,6 +143,19 @@ 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 = [ + # Generated spec-oracle modules carry two emission shapes pyright rejects + # but pydantic handles correctly, and no semantics-preserving spelling + # avoids either: (1) the schema's named-error defs require `data` while the + # base JSON-RPC error object has it optional - the required-field override + # is right on the wire (the requiredness audit in + # scripts/update_spec_types.py pins it) but trips the dataclass-transform + # rule (reportGeneralTypeIssues); (2) the draft schema's recursive + # JSONValue/JSONObject alias group must be emitted as a whole-string + # TypeAliasType value for lazy parsing, which pyright flags at its use + # sites (reportInvalidTypeForm, reportUnknownVariableType). + { root = "tests/spec_oracles", extraPaths = [ + ".", + ], reportUnusedFunction = false, reportPrivateUsage = false, reportGeneralTypeIssues = false, reportInvalidTypeForm = false, reportUnknownVariableType = false }, { root = "tests", extraPaths = [ ".", ], reportUnusedFunction = false, reportPrivateUsage = false }, @@ -177,6 +190,9 @@ 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/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..cd7313f7dd --- /dev/null +++ b/scripts/update_spec_types.py @@ -0,0 +1,1446 @@ +"""Regenerate the spec-version model modules (test oracles and src packages). + +Two output flavors share one fetch -> generate -> post-process core: + +``--oracle`` (default) regenerates 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. + +``--src`` scaffolds the committed per-version wire-model packages under +src/mcp/types/v/. Same core, then the source passes documented on +each pass function below (value-type downgrades, per-class extra policy, +``_meta``-carrier rebasing, inheritance flattening, deterministic synthetic +names, early content arms, docstring wrapping, provenance docstring), a strict +ruff stage (no residual findings at all), and the import-time audits. It is a +scaffold, not a sync mechanism: it refuses to overwrite an existing package +without ``--force``, and the emitted packages are maintained as ordinary +source afterwards. + +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. Default: all non-frozen entries. + --all select every entry. + --src emit src/mcp/types/v/__init__.py packages instead + of test oracles. + --sha SHA override the pinned SHA for the (single) selected entry and + write it back to PINNED.json on success (oracle mode only). + --schema-dir D offline mode: read schemas from D instead of fetching. + Naming: schema-.json / schema-.ts. 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) oracle entries, + or overwriting an existing src package. +""" + +from __future__ import annotations + +import argparse +import ast +import importlib.util +import json +import re +import subprocess +import sys +import tempfile +import textwrap +import typing +from dataclasses import dataclass, field +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" +SRC_TYPES_DIR = REPO_ROOT / "src" / "mcp" / "types" +PINNED_PATH = ORACLES_DIR / "PINNED.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": {}, +} + +_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*") +_FIELD_ALIAS_RE = re.compile(r"alias=['\"]([^'\"]+)['\"]") + +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 = "" + src_notes: list[str] = field(default_factory=list) + + +@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]: + """Return (schema.json text, schema.ts text) for an entry.""" + if schema_dir is not None: + 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}") + if not ts_path.is_file(): + raise PipelineError(f"{entry.key}: --schema-dir is missing {ts_path.name}") + return json_path.read_text(), ts_path.read_text() + schema_json = gh_fetch(entry.repo, entry.schema_path, sha) + 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) -> str: + """Extract LATEST_PROTOCOL_VERSION from schema.ts.""" + 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(protocol_version: str) -> str: + """Derive the module name from the protocol version string.""" + return "v" + re.sub(r"[^a-z0-9]+", "_", protocol_version.lower()) + + +def run_generator(schema_json_path: Path, output_path: Path, generator_version: str, base_class: 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", + base_class, + "--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) + # Pure insertion AFTER the last import line (start = end + 1), never a + # replacement of it: the import line itself may carry its own + # replacement (dropping RootModel), and two edits to one line range + # silently clobber each other. + replacements.append((last_import_end + 1, last_import_end, "\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 assert_alias_blocks_present(source: str, recursive: list[str]) -> None: + """Assert every recursive alias survived post-processing into the output. + + Guards the relocation edit: a silently dropped alias block would only + surface much later as a NameError at import time of some consumer. + """ + for name in recursive: + if not re.search(rf'^{name} = TypeAliasType\("{name}"', source, flags=re.MULTILINE): + raise PipelineError(f"recursive alias {name!r} is missing from the post-processed output") + + +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, *, allow_e501: bool) -> int: + """ruff check --fix + format with the repo config; return the residual E501 count. + + Oracle mode tolerates residual E501 (per-file-ignored for the generated + test modules once installed); src mode does not — the docstring-wrap pass + must leave nothing over the line limit. Any other residual finding aborts + the pipeline in both modes. + """ + 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()}") + if not allow_e501: + # Strict mode reports after formatting: long code lines are the + # formatter's job, and only what it cannot wrap should fail the run. + pre_fmt = subprocess.run( + [*common, "format", "--no-cache", str(path)], cwd=REPO_ROOT, capture_output=True, text=True + ) + if pre_fmt.returncode != 0: + raise PipelineError(f"ruff format failed:\n{pre_fmt.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 allow_e501 else findings + 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 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 import_candidate(path: Path, module: str) -> ModuleType: + """Import a staged candidate module under a private name.""" + name = f"_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] = [] + 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 + 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: + 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 _annotation_models(annotation: Any) -> list[type[BaseModel]]: + """Model classes appearing anywhere in an annotation.""" + if isinstance(annotation, type) and issubclass(annotation, BaseModel): + return [annotation] + found: list[type[BaseModel]] = [] + for arg in typing.get_args(annotation): + found.extend(_annotation_models(arg)) + return found + + +def audit_meta_carriers(mod: ModuleType, entry: Entry) -> None: + """Assert every `_meta` field is an open mapping: a dict or an extra="allow" model. + + A `_meta` carrier that silently dropped unknown keys would lose vendor + metadata on revalidation; the per-class extra policy must never close one. + """ + problems: list[str] = [] + for name in dir(mod): + obj = getattr(mod, name) + if not (isinstance(obj, type) and issubclass(obj, BaseModel)): + continue + for field_name, info in obj.model_fields.items(): + if (info.serialization_alias or info.alias or field_name) != "_meta": + continue + problems.extend( + f"{name}._meta references {referenced.__name__}, which is not extra='allow'" + for referenced in _annotation_models(info.annotation) + if referenced.model_config.get("extra") != "allow" + ) + if problems: + raise PipelineError(f"{entry.key}: _meta carrier audit failed:\n" + "\n".join(f" - {p}" for p in problems)) + + +def import_check(import_path: 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 {import_path}"], + cwd=REPO_ROOT, + capture_output=True, + text=True, + ) + if result.returncode != 0: + raise PipelineError(f"import check failed for {import_path}:\n{result.stderr.strip()}") + + +def build_candidate(entry: Entry, sha: str, schema_dir: Path | None, generator_version: str) -> tuple[str, Outcome]: + """Oracle-mode 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(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, "tests.spec_oracles._base.OracleModel") + processed = postprocess(raw_file.read_text(), rename_map) + assert_alias_blocks_present(processed.source, processed.recursive) + + 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, allow_e501=True) + 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 + + +# --------------------------------------------------------------------------- +# src mode: the committed per-version wire-model packages +# --------------------------------------------------------------------------- + +SRC_HEADER_TEMPLATE = '''\ +"""Internal wire-shape models for protocol {protocol_version}. Not part of the public API. + +Initially generated from {schema_path} @ {sha} by +``scripts/update_spec_types.py --src`` (datamodel-code-generator +{generator_version}), then hand-validated against the pinned schema. +Maintained as ordinary source: edits are permitted, but +``tests/types/test_version_model_parity.py`` pins every definition against the +generated spec oracle for this version — a drifting edit fails CI. Prefer +fixing the generator pass and re-scaffolding over hand-patching. + +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. +{version_notes} +Models live in this package's ``__init__.py`` so the whole version reads as +one file beside its pinned schema; the package form leaves room for a future +per-family split without import-path churn. +""" +''' + +# Appended to the 2026-07-28 package docstring: at that revision the reserved +# `_meta` interiors validate through typed models, so revalidation drops +# vendor extras nested inside those two values (a recorded, deliberate cost — +# a transitive extra="allow" closure would leak superset fields instead). +_DRAFT_INTERIOR_NOTE = """ +At this revision the ``clientInfo``/``clientCapabilities`` values inside a +request's ``_meta`` validate through typed models, so vendor extra keys +nested inside those two values do not survive revalidation. +""" + +_SRC_BASE_CLASS = "mcp.types._wire_base.WireModel" +_SRC_BASE_NAMES = frozenset({"WireModel", "OpenWireModel"}) + +# Wire names whose referenced classes stay extra="allow": schema-open +# pass-through interiors whose unknown keys must survive revalidation. +_OPEN_INTERIOR_FIELD_ALIASES = frozenset({"inputSchema", "outputSchema"}) +_OPEN_INTERIOR_COMMENT = " # Stays open: schema keywords beyond the declared properties ride extra fields.\n" +_SUBSCRIPTION_FILTER_COMMENT = " # Stays open: filter contents are extensible on the wire.\n" + +# Content-block definitions carried into packages older than the schema that +# introduces them, so serializing such a block to an older peer passes it +# through instead of refusing. Class text matches the generated form in the +# first defining version, after the value-type downgrade (base64 -> str). +_T5_COMMENT = ( + "# Not in this version's schema ({intro_version} introduced it): the SDK emits\n" + "# this content block to older peers unchanged rather than refusing. The only\n" + "# content arms deliberately absent from older packages are the tool blocks\n" + "# added in 2025-11-25.\n" +) +_AUDIO_CONTENT_TEMPLATE = '''\ +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"] +''' +_RESOURCE_LINK_TEMPLATE = '''\ +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. + """ +''' + + +def downgrade_value_transforms(source: str) -> str: + """Replace value-transforming pydantic types with plain ``str``. + + URL normalization and base64 re-encoding change bytes on a validate -> + re-dump round trip; the wire models must reproduce input bytes exactly. + """ + source = re.sub(r"\b(AnyUrl|FileUrl|Base64Str)\b", "str", source) + # Drop the now-unused names from the pydantic import (ruff would flag them). + match = re.search(r"^from pydantic import (.+)$", source, flags=re.MULTILINE) + if match: + names = [n.strip() for n in match.group(1).split(",")] + kept = [n for n in names if n != "str"] + source = source.replace(match.group(0), f"from pydantic import {', '.join(kept)}") + return source + + +def widen_structured_content(source: str) -> str: + """Widen typed ``structuredContent`` fields to ``Any``. + + The newest schema types the field ``Any``; older schemas say + ``dict[str, Any]``. The wire models never narrow values an SDK model + accepts, so a scalar structured result passes through at every version. + """ + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + statements = [ + stmt + for node in tree.body + if isinstance(node, ast.ClassDef) + for stmt in node.body + if isinstance(stmt, ast.AnnAssign) + and isinstance(stmt.target, ast.Name) + and stmt.target.id == "structured_content" + ] + for stmt in sorted(statements, key=lambda s: s.lineno, reverse=True): + start, end = stmt.lineno, _end_lineno(stmt) + segment = "".join(lines[start - 1 : end]) + lines[start - 1 : end] = [segment.replace("dict[str, Any]", "Any")] + return "".join(lines) + + +def _class_field_aliases(node: ast.ClassDef) -> list[tuple[ast.AnnAssign, str, str]]: + """(statement, field name, wire alias) for every annotated field of a class.""" + out: list[tuple[ast.AnnAssign, str, str]] = [] + for stmt in node.body: + if not isinstance(stmt, ast.AnnAssign) or not isinstance(stmt.target, ast.Name): + continue + annotation_text = ast.unparse(stmt.annotation) + alias_match = _FIELD_ALIAS_RE.search(annotation_text) + alias = alias_match.group(1) if alias_match else stmt.target.id + out.append((stmt, stmt.target.id, alias)) + return out + + +def flatten_inheritance(source: str) -> tuple[str, list[str]]: + """Flatten classes that subclass other generated classes. + + The generator emits subclassing when one definition narrows another (the + named-error family, the 2025-11-25 task results); pyright's + dataclass-transform rules reject the narrowed overrides, and the committed + packages carry no suppressions. Flattening re-declares the inherited + fields directly: base fields in base order, overrides in place, own fields + appended. + """ + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + + def field_segments(node: ast.ClassDef) -> list[tuple[str, str]]: + """(field name, source text incl. trailing docstring) per field.""" + segments: list[tuple[str, str]] = [] + body = list(node.body) + for index, stmt in enumerate(body): + if not isinstance(stmt, ast.AnnAssign) or not isinstance(stmt.target, ast.Name): + continue + end = _end_lineno(stmt) + if index + 1 < len(body): + nxt = body[index + 1] + if isinstance(nxt, ast.Expr) and isinstance(nxt.value, ast.Constant): + if isinstance(nxt.value.value, str): + end = _end_lineno(nxt) + segments.append((stmt.target.id, "".join(lines[stmt.lineno - 1 : end]))) + return segments + + resolved: dict[str, list[tuple[str, str]]] = {} + + def resolve(name: str) -> list[tuple[str, str]]: + if name in resolved: + return resolved[name] + node = classes[name] + base_names = [b.id for b in node.bases if isinstance(b, ast.Name) and b.id in classes] + merged: list[tuple[str, str]] = [] + for base in base_names: + for field_name, text in resolve(base): + if field_name not in {n for n, _ in merged}: + merged.append((field_name, text)) + for field_name, text in field_segments(node): + existing = [i for i, (n, _) in enumerate(merged) if n == field_name] + if existing: + merged[existing[0]] = (field_name, text) + else: + merged.append((field_name, text)) + resolved[name] = merged + return merged + + flattened: list[str] = [] + replacements: list[tuple[int, int, str]] = [] + for name, node in classes.items(): + generated_bases = [b.id for b in node.bases if isinstance(b, ast.Name) and b.id in classes] + if not generated_bases: + continue + flattened.append(name) + parts: list[str] = [f"class {name}(WireModel):\n"] + if ast.get_docstring(node) is not None: + stmt = node.body[0] + parts.append("".join(lines[stmt.lineno - 1 : _end_lineno(stmt)])) + parts.extend( + "".join(lines[stmt.lineno - 1 : _end_lineno(stmt)]) + for stmt in node.body + if isinstance(stmt, ast.Assign) + and any(isinstance(t, ast.Name) and t.id == "model_config" for t in stmt.targets) + ) + parts.extend(text for _, text in resolve(name)) + replacements.append((node.lineno, _end_lineno(node), "".join(parts))) + + for start, end, new in sorted(replacements, reverse=True): + lines[start - 1 : end] = [new] + return "".join(lines), sorted(flattened) + + +def rebase_meta_carriers(source: str) -> tuple[str, list[str]]: + """Rebase classes referenced from ``_meta`` fields onto ``OpenWireModel``. + + Unknown ``_meta`` keys must survive a validate -> re-dump round trip at + every version, so their carrier classes keep ``extra="allow"`` through the + open base rather than a per-class config. + """ + tree = ast.parse(source) + classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + carriers: set[str] = set() + for node in classes.values(): + for stmt, _, alias in _class_field_aliases(node): + if alias != "_meta": + continue + annotation_text = ast.unparse(stmt.annotation) + carriers.update(name for name in classes if re.search(rf"\b{name}\b", annotation_text)) + for name in sorted(carriers): + source = re.sub( + rf"^class {name}\(WireModel\):$", + f"class {name}(OpenWireModel):", + source, + flags=re.MULTILINE, + ) + return source, sorted(carriers) + + +def apply_extra_policy(source: str, carriers: list[str]) -> tuple[str, list[str]]: + """Force ``extra="ignore"`` everywhere except the enumerated open classes. + + The generator emits per-class ``extra="allow"`` wherever the schema has an + index signature. Open models would retain fields the target version never + defined, so a version-mismatched field would never register as a loss on + revalidation; closing them is what makes those fields detectable. The + classes that stay open are exactly: ``_meta`` carriers (via the open + base), the tool input/output schema interiors, and the subscription + filter. + """ + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + + keep_allow: set[str] = set() + for node in classes.values(): + for stmt, _, alias in _class_field_aliases(node): + if alias in _OPEN_INTERIOR_FIELD_ALIASES: + annotation_text = ast.unparse(stmt.annotation) + keep_allow.update(name for name in classes if re.search(rf"\b{name}\b", annotation_text)) + if "SubscriptionFilter" in classes: + keep_allow.add("SubscriptionFilter") + + replacements: list[tuple[int, int, str]] = [] + for name, node in classes.items(): + config_stmt = next( + ( + stmt + for stmt in node.body + if isinstance(stmt, ast.Assign) + and any(isinstance(t, ast.Name) and t.id == "model_config" for t in stmt.targets) + ), + None, + ) + if config_stmt is None: + continue + value = config_stmt.value + is_plain_allow = ( + isinstance(value, ast.Call) + and isinstance(value.func, ast.Name) + and value.func.id == "ConfigDict" + and len(value.keywords) == 1 + and value.keywords[0].arg == "extra" + and isinstance(value.keywords[0].value, ast.Constant) + and value.keywords[0].value.value == "allow" + ) + if not is_plain_allow: + raise PipelineError(f"unexpected model_config on {name}: {ast.unparse(config_stmt)}") + if name in keep_allow and name not in carriers: + comment = _SUBSCRIPTION_FILTER_COMMENT if name == "SubscriptionFilter" else _OPEN_INTERIOR_COMMENT + replacements.append((config_stmt.lineno, config_stmt.lineno - 1, comment)) + continue + remaining = [stmt for stmt in node.body if stmt is not config_stmt] + new = "" if remaining else " pass\n" + replacements.append((config_stmt.lineno, _end_lineno(config_stmt), new)) + + for start, end, new in sorted(replacements, reverse=True): + lines[start - 1 : end] = [new] + return "".join(lines), sorted(keep_allow) + + +def deterministic_synthetic_names(source: str) -> tuple[str, dict[str, str], list[str]]: + """Rename counter-based synthetic classes after their single owner. + + The generator names inline params/meta objects ``Params``/``Params1``/... + in discovery order, so an inserted definition renumbers every later one + and pollutes cross-version diffs. A synthetic class referenced by exactly + one field of one owner is renamed ``Params``/``Meta``; + shared or colliding names keep their generated form. + """ + tree = ast.parse(source) + classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + 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 node in classes.values(): + for stmt, field_name, _ in _class_field_aliases(node): + annotation_text = ast.unparse(stmt.annotation) + for name in synthetic: + if re.search(rf"\b{name}\b", annotation_text): + references[name].append((node.name, field_name)) + + renames: dict[str, str] = {} + skipped: list[str] = [] + taken = set(classes) + for name in sorted(synthetic): + refs = references[name] + if len(refs) != 1: + skipped.append(f"{name} ({len(refs)} references)") + continue + owner, field_name = refs[0] + suffix = {"params": "Params", "meta": "Meta"}.get(field_name) + if suffix is None: + skipped.append(f"{name} (referenced from field {field_name!r})") + continue + target = f"{owner}{suffix}" + if target in taken: + skipped.append(f"{name} (target {target} already exists)") + continue + renames[name] = target + taken.add(target) + + if renames: + pattern = re.compile(r"\b(" + "|".join(sorted(renames, key=len, reverse=True)) + r")\b") + source = pattern.sub(lambda m: renames[m.group(1)], source) + return source, renames, skipped + + +def add_early_content_arms(source: str, protocol_version: str) -> tuple[str, list[str]]: + """Carry newer content-block definitions into the older packages. + + ``audio`` (2025-03-26) and ``resource_link`` (2025-06-18) content emitted + to an older peer passes through unchanged rather than being refused, so + the older packages carry the definitions and union arms early. + """ + added: list[str] = [] + if protocol_version == "2024-11-05": + source = _splice_class_after( + source, + "ImageContent", + _T5_COMMENT.format(intro_version="2025-03-26") + _AUDIO_CONTENT_TEMPLATE, + ) + source = _add_union_arm(source, "TextContent | ImageContent", "TextContent | ImageContent | AudioContent") + added.append("AudioContent") + if protocol_version in ("2024-11-05", "2025-03-26"): + source = _splice_class_after( + source, + "EmbeddedResource", + _T5_COMMENT.format(intro_version="2025-06-18") + _RESOURCE_LINK_TEMPLATE, + ) + source = _add_union_arm(source, "| EmbeddedResource", "| ResourceLink | EmbeddedResource") + added.append("ResourceLink") + return source, added + + +def _splice_class_after(source: str, anchor_class: str, class_text: str) -> str: + """Insert a class definition immediately after ``anchor_class``.""" + tree = ast.parse(source) + anchor = next((n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == anchor_class), None) + if anchor is None: + raise PipelineError(f"cannot splice after {anchor_class!r}: class not found") + lines = source.splitlines(keepends=True) + end = _end_lineno(anchor) + lines[end:end] = ["\n\n" + class_text] + return "".join(lines) + + +def _add_union_arm(source: str, old: str, new: str) -> str: + """Widen every content union matching ``old`` and comment the change.""" + note = " # Carries arms beyond this version's schema; see the class comments above.\n" + out_lines: list[str] = [] + for line in source.splitlines(keepends=True): + if old in line and not line.lstrip().startswith(("#", "class ")): + if not out_lines or out_lines[-1] != note: + out_lines.append(note) + line = line.replace(old, new) + out_lines.append(line) + return "".join(out_lines) + + +def _join_class_docstring_summaries(source: str) -> str: + """Move each class docstring summary onto the opening quote line. + + This is the fix the D212 lint rule would apply later; doing it first + keeps the long-line wrapping below in charge of the resulting line + length. Field annotation strings are not docstrings and stay untouched. + """ + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + for node in tree.body: + if not isinstance(node, ast.ClassDef) or ast.get_docstring(node) is None: + continue + stmt = node.body[0] + first = lines[stmt.lineno - 1] + if first.strip() != '"""' or _end_lineno(stmt) == stmt.lineno: + continue + summary = lines[stmt.lineno].strip() + single_content = _end_lineno(stmt) == stmt.lineno + 2 + indent = first[: len(first) - len(first.lstrip())] + if single_content: + # The formatter collapses a one-line docstring body onto a single + # line; emit that form directly when it fits, otherwise pre-wrap + # the summary so the body stays multi-line. + if len(indent) + len(summary) + 6 <= 120: + lines[stmt.lineno - 1] = f'{indent}"""{summary}"""\n' + lines[stmt.lineno] = "" + lines[stmt.lineno + 1] = "" + else: + # Width leaves room for the closing quotes in case the wrap + # still fits on one line and the formatter collapses it. + wrapped = textwrap.wrap( + summary, + width=117, + initial_indent=indent + '"""', + subsequent_indent=indent, + break_long_words=False, + break_on_hyphens=False, + ) + lines[stmt.lineno - 1] = "".join(part + "\n" for part in wrapped) + lines[stmt.lineno] = "" + continue + lines[stmt.lineno - 1] = first.rstrip("\n") + summary + "\n" + lines[stmt.lineno] = "" + return "".join(lines) + + +def wrap_docstrings(source: str) -> str: + """Wrap docstring lines to the repo line length. + + The schema prose stays (the file should read as the spec), but committed + src modules carry no per-file lint exemptions, so long lines wrap. Both + body lines and opening-quote summary lines are wrapped; anything this + pass cannot handle fails the strict ruff stage. + """ + source = _join_class_docstring_summaries(source) + out: list[str] = [] + in_docstring = False + for line in source.splitlines(keepends=True): + quotes = line.count('"""') + long = len(line.rstrip("\n")) > 120 + indent = line[: len(line) - len(line.lstrip())] + if not in_docstring: + if quotes == 1: + in_docstring = True + if long: + out.extend( + part + "\n" + for part in textwrap.wrap( + line.strip().removeprefix('"""'), + width=120, + initial_indent=indent + '"""', + subsequent_indent=indent, + break_long_words=False, + break_on_hyphens=False, + ) + ) + continue + out.append(line) + continue + if quotes: + in_docstring = False + out.append(line) + continue + if not long: + out.append(line) + continue + out.extend( + part + "\n" + for part in textwrap.wrap( + line.strip(), + width=120, + initial_indent=indent, + subsequent_indent=indent, + break_long_words=False, + break_on_hyphens=False, + ) + ) + return "".join(out) + + +def assert_only_wire_bases(source: str) -> None: + """Assert every class in the emitted package sits on one of the two bases.""" + tree = ast.parse(source) + for node in tree.body: + if not isinstance(node, ast.ClassDef): + continue + base_names = [ast.unparse(b) for b in node.bases] + if base_names not in (["WireModel"], ["OpenWireModel"]): + raise PipelineError(f"class {node.name} has unexpected bases {base_names} after the src passes") + + +def build_src_candidate(entry: Entry, sha: str, schema_dir: Path | None, generator_version: str) -> tuple[str, Outcome]: + """src-mode pipeline for one entry; returns (candidate source, outcome).""" + if importlib.util.find_spec("mcp.types._wire_base") is None: + raise PipelineError("src mode requires src/mcp/types/_wire_base.py (WireModel/OpenWireModel) to exist") + 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(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, _SRC_BASE_CLASS) + processed = postprocess(raw_file.read_text(), rename_map) + assert_alias_blocks_present(processed.source, processed.recursive) + + notes: list[str] = [] + source = downgrade_value_transforms(processed.source) + source = widen_structured_content(source) + source, flattened = flatten_inheritance(source) + if flattened: + notes.append(f"flattened: {', '.join(flattened)}") + source, carriers = rebase_meta_carriers(source) + if carriers: + notes.append(f"meta carriers on OpenWireModel: {', '.join(carriers)}") + source = source.replace( + "from mcp.types._wire_base import WireModel", + "from mcp.types._wire_base import OpenWireModel, WireModel", + ) + source, keep_allow = apply_extra_policy(source, carriers) + if keep_allow: + notes.append(f"extra=allow kept: {', '.join(sorted(set(keep_allow) - set(carriers)))}") + source, renames, skipped = deterministic_synthetic_names(source) + notes.append(f"synthetic renames: {len(renames)}, kept generated names: {len(skipped)}") + if skipped: + notes.append(f"kept: {'; '.join(skipped)}") + source, t5 = add_early_content_arms(source, protocol_version) + if t5: + notes.append(f"early content arms: {', '.join(t5)}") + source = wrap_docstrings(source) + + header = SRC_HEADER_TEMPLATE.format( + protocol_version=protocol_version, + schema_path=entry.schema_path, + sha=sha, + generator_version=generator_version, + version_notes=_DRAFT_INTERIOR_NOTE if protocol_version == "2026-07-28" else "", + ) + staging = SRC_TYPES_DIR / f"_staging_{entry.module}.py" + try: + staging.write_text(header + source) + run_ruff(staging, allow_e501=False) + candidate = staging.read_text() + assert_only_wire_bases(candidate) + mod = import_candidate(staging, entry.module) + audit_candidate(mod, schema, entry, rename_map) + audit_meta_carriers(mod, entry) + 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=0, + src_notes=notes, + ) + 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 oracle 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], *, src: bool) -> None: + """Print the per-entry generation summary.""" + for o in outcomes: + target = f"src/mcp/types/{o.entry.module}/__init__.py" if src else f"tests/spec_oracles/{o.entry.module}.py" + print( + f"{o.entry.key}: {o.action} -> {target} " + f"(defs={o.def_count}, alias_conversions={o.converted_aliases}, " + f"recursive={o.recursive_aliases or 'none'}, residual_E501={o.e501_count})" + ) + for note in o.src_notes: + print(f" {note}") + 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("--src", action="store_true", help="emit src/mcp/types/v/ packages") + 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="regenerate frozen entries / overwrite src packages") + 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] + elif args.src: + selected = entries + else: + selected = [e for e in entries if not e.frozen] + if args.sha and (len(selected) != 1 or args.src): + parser.error("--sha requires exactly one KEY in oracle mode") + + generator_version: str = pinned["generator"]["version"] + outcomes: list[Outcome] = [] + drifted: list[str] = [] + regenerated: list[Entry] = [] + for entry in selected: + if args.src: + candidate, outcome = build_src_candidate(entry, entry.sha, args.schema_dir, generator_version) + target = SRC_TYPES_DIR / entry.module / "__init__.py" + if args.check: + if target.is_file() and target.read_text() == candidate: + outcome.action = "checked" + else: + outcome.action = "drifted" + drifted.append(entry.module) + elif target.is_file() and not args.force: + outcome.action = "exists (scaffold refused; pass --force to overwrite)" + else: + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(candidate) + outcome.action = "installed" + import_check(f"mcp.types.{entry.module}") + outcomes.append(outcome) + continue + 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(f"tests.spec_oracles.{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, src=args.src) + 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..9833c1f9b4 100644 --- a/src/mcp/shared/version.py +++ b/src/mcp/shared/version.py @@ -16,8 +16,13 @@ "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. +""" 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..8c8ecf7ada --- /dev/null +++ b/src/mcp/types/_spec_names.py @@ -0,0 +1,136 @@ +"""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 is the single +record of every deliberate divergence, consumed by the spec-comparison tests — +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``: public ``mcp.types`` names with no schema counterpart in + any protocol version. +""" + +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", +} + +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] + # `_meta` containers: the SDK carries them as open mappings (`Meta`, + # `RequestParamsMeta`), and the wire boundary manages the reserved keys. + "MetaObject": "nominal-alias-covered-by-dict", + "RequestMetaObject": "meta-managed-at-boundary", + # @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. + # Alternative considered: modeling 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..dc6fefe7bc 100644 --- a/src/mcp/types/_types.py +++ b/src/mcp/types/_types.py @@ -1,20 +1,43 @@ +"""The version-superset ("monolith") MCP protocol models. + +One model per protocol construct, carrying every field from every supported +protocol version — except deliberate deferrals recorded in +``tests/spec_oracles/burndown_allowlist.json`` — so application code handles +a single set of types no matter which version a session negotiated. +Per-field docstrings record where a field's availability differs across +versions. ``mcp.types.wire`` is the boundary that serializes these models +to — and parses wire data from — the exact shape of a negotiated version, +using the per-version model packages (``mcp.types.v*``) as the source of +each version's wire shape. +""" + from __future__ import annotations -from typing import Annotated, Any, Generic, Literal, TypeAlias, TypeVar +from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar -from pydantic import BaseModel, ConfigDict, Field, FileUrl, TypeAdapter +from pydantic import ( + BaseModel, + ConfigDict, + Field, + FileUrl, + TypeAdapter, +) from pydantic.alias_generators import to_camel from typing_extensions import NotRequired, TypedDict from mcp.types.jsonrpc import RequestId -LATEST_PROTOCOL_VERSION = "2025-11-25" -"""The latest version of the Model Context Protocol. +LATEST_PROTOCOL_VERSION: Final[str] = "2025-11-25" +"""The newest protocol version this SDK can negotiate. You can find the latest specification at https://modelcontextprotocol.io/specification/latest. + +This is deliberately `Final[str]`, not a `Literal`: the value advances when SDK +support for a newer protocol revision ships, so callers must not narrow on the +current value. """ -DEFAULT_NEGOTIATED_VERSION = "2025-03-26" +DEFAULT_NEGOTIATED_VERSION: Final[str] = "2025-03-26" """The default negotiated version of the Model Context Protocol when no version is specified. We need this to satisfy the MCP specification, which requires the server to assume a specific version if none is @@ -25,9 +48,23 @@ """ ProgressToken = str | int +"""A progress token, used to associate progress notifications with the original request. + +Identical in every supported protocol version: a string or a number (the JSON form of +every schema version pins the numeric kind to integer; null is never allowed). A +requester places the token in a request's optional ``_meta.progressToken`` slot; the +receiver attaches the same token as the required ``progressToken`` field of any +``notifications/progress`` it chooses to emit, correlating the notification stream +back to the original request. +""" Role = Literal["user", "assistant"] +"""The sender or recipient of messages and data in a conversation. + +The value set is identical in every protocol version (2024-11-05 through 2026-07-28). +""" IconTheme = Literal["light", "dark"] +"""Theme an icon is designed for. Wire values of ``Icon.theme`` (2025-11-25+).""" class MCPModel(BaseModel): @@ -38,8 +75,47 @@ 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). + +SDK-managed: injected by the client on 2026-07-28 sessions. 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). + +SDK-managed: injected by the client per request on 2026-07-28 sessions. +""" + +CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities" +"""Reserved request `_meta` key: the client's per-request `ClientCapabilities` (2026-07-28). + +SDK-managed: injected by the client per request on 2026-07-28 sessions; 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 are SDK-managed session/request + state on 2026-07-28 sessions: the session layer supplies + clientInfo/clientCapabilities, and the wire boundary injects + protocolVersion and validates all three. Read or set them via the + ``*_META_KEY`` constants. + """ + progress_token: NotRequired[ProgressToken] """ If specified, the caller requests out-of-band progress notifications for @@ -50,10 +126,25 @@ class RequestParamsMeta(TypedDict, extra_items=Any): class RequestParams(MCPModel): + """Common params for any request.""" + meta: RequestParamsMeta | None = Field(alias="_meta", default=None) + """Metadata reserved by MCP for protocol-level concerns (wire name `_meta`). + + Carries the optional progress token and, on 2026-07-28 sessions, the reserved + `io.modelcontextprotocol/*` keys (protocolVersion, clientInfo, + clientCapabilities, plus the deprecated logLevel). The field is required on + the wire for 2026-07-28 client requests: the session layer supplies the + clientInfo/clientCapabilities entries, and the wire boundary injects only + protocolVersion and refuses to serialize a client request missing the other + two. Code sending requests through an SDK session normally leaves this + field unset. + """ class PaginatedRequestParams(RequestParams): + """Common params for paginated requests.""" + cursor: str | None = None """An opaque token representing the current pagination position. @@ -62,6 +153,8 @@ class PaginatedRequestParams(RequestParams): class NotificationParams(MCPModel): + """Common params for any notification.""" + 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) @@ -75,23 +168,64 @@ class NotificationParams(MCPModel): class Request(MCPModel, Generic[RequestParamsT, MethodT]): - """Base class for JSON-RPC requests.""" + """Base class for JSON-RPC requests. + + Concrete requests subclass this as + ``Request[, Literal[""]]`` and default the method + literal. The JSON-RPC envelope (``jsonrpc``, ``id``) is not part of this + payload type; it is attached by the session layer (see ``mcp.types.jsonrpc``). + """ method: MethodT + """The protocol method name identifying this request.""" + params: RequestParamsT + """The request's parameters; concrete subclasses set the per-method type and + requiredness.""" class PaginatedRequest(Request[PaginatedRequestParams | None, MethodT], Generic[MethodT]): """Base class for paginated requests, matching the schema's PaginatedRequest interface.""" params: PaginatedRequestParams | None = None + """Pagination params. + + Required on the wire for 2026-07-28 peers (because `_meta` is required + there): the session layer supplies the clientInfo/clientCapabilities + `_meta` entries, and the wire boundary materializes ``params``, injects + protocolVersion, and refuses to serialize without the identity entries. + Optional on all earlier versions. + """ class Notification(MCPModel, Generic[NotificationParamsT, MethodT]): """Base class for JSON-RPC notifications.""" method: MethodT + """The notification method name.""" + params: NotificationParamsT + """The notification's parameters. + + Optional on the wire in every protocol version; concrete subclasses narrow + this to their params model, or to `NotificationParams | None = None` for + parameterless notifications. + """ + + +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): @@ -103,8 +237,24 @@ 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 peer did not send the field (pre-2026-07-28 peers never do), + which the spec defines as equivalent to "complete". The SDK injects this on + 2026-07-28 emission and strips it when emitting to earlier versions; handlers + normally never set it. + """ + 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 +262,46 @@ class PaginatedResult(Result): """ +class CacheableResult(Result): + """Base class for results that carry client-side caching directives (2026-07-28). + + Both fields are required on the wire for 2026-07-28 peers; the SDK supplies + defaults at the wire boundary when a handler leaves them unset. The fields do + not exist on the wire for 2025-11-25 and earlier sessions. + """ + + 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 the handler left it unset; on 2026-07-28 + sessions the SDK supplies a value at emit time. + """ + + 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 the handler left it unset; on + 2026-07-28 sessions the SDK supplies a value at emit time. + """ + + 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,50 +315,95 @@ 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. """ class Implementation(BaseMetadata): - """Describes the name and version of an MCP implementation.""" + """Describes the MCP implementation (``clientInfo`` / ``serverInfo``). - version: str + On sessions negotiated at 2025-11-25 or earlier, this is carried once per + session: client->server as ``InitializeRequestParams.client_info`` and + server->client as ``InitializeResult.server_info``. On 2026-07-28 sessions it + is carried per request in ``_meta["io.modelcontextprotocol/clientInfo"]`` and + server->client as ``DiscoverResult.server_info``. - title: str | None = None - """An optional human-readable title for this implementation.""" + Inherits ``name`` (required) and optional ``title`` from ``BaseMetadata``; + only ``name`` and ``version`` are required on the wire in every protocol + version. + """ + + version: str + """The version of this implementation.""" description: str | None = None - """An optional human-readable description of what this implementation does.""" + """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. + """ website_url: str | None = None """An optional URL of the website for this implementation.""" icons: list[Icon] | None = None - """An optional list of icons for this implementation.""" + """Optional set of sized icons that the client can display in a user interface.""" class RootsCapability(MCPModel): - """Capability for root operations.""" + """Capability for root operations. + + Deprecated as a whole in protocol 2026-07-28 (SEP-2577) but kept in the + specification's deprecated-features registry, so 2026-07-28 sessions still + carry it on the wire — as an empty object, since ``list_changed`` below + exists only through 2025-11-25. + """ 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): @@ -201,7 +427,10 @@ class FormElicitationCapability(MCPModel): class UrlElicitationCapability(MCPModel): - """Capability for URL mode elicitation.""" + """Capability for URL mode elicitation. + + New in protocol 2025-11-25, with URL mode itself (no earlier wire form). + """ class ElicitationCapability(MCPModel): @@ -214,11 +443,15 @@ class ElicitationCapability(MCPModel): """Present if the client supports form mode elicitation.""" url: UrlElicitationCapability | None = None - """Present if the client supports URL mode elicitation.""" + """Present if the client supports URL mode elicitation (2025-11-25 and later).""" class SamplingCapability(MCPModel): - """Sampling capability structure, allowing fine-grained capability advertisement.""" + """Sampling capability structure, allowing fine-grained capability advertisement. + + The `sampling` capability as a whole is deprecated in protocol 2026-07-28 + (SEP-2577) but its shape is unchanged there; used on all sessions. + """ context: SamplingContextCapability | None = None """ @@ -232,8 +465,92 @@ 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.""" + """Capabilities a client may support. + + Known capabilities are defined in the spec schema, but this is not a closed + set: any client can define its own, additional capabilities. On protocol + versions through 2025-11-25 this object is sent once, in `initialize`; on + 2026-07-28 sessions the SDK carries it in every request's `_meta` under + "io.modelcontextprotocol/clientCapabilities". + """ experimental: dict[str, dict[str, Any]] | None = None """Experimental, non-standard capabilities that the client supports.""" @@ -246,6 +563,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,14 +635,65 @@ 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.""" + """Capabilities that a server may support. + + Known capabilities are defined here, 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: LoggingCapability | None = None - """Present if the server supports sending log messages to the client.""" + """Present if the server supports sending log messages to the client. + + Deprecated as of protocol version 2026-07-28 (SEP-2577); remains valid on + earlier-version sessions. + """ prompts: PromptsCapability | None = None """Present if the server offers any prompt templates.""" @@ -300,39 +707,95 @@ 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. +# Alternative considered: moving the 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.""" + """The latest version of the Model Context Protocol that the client supports. + + The client MAY decide to support older versions as well. + """ + capabilities: ClientCapabilities + """The capabilities the client supports.""" + client_info: Implementation + """Information about the client implementation.""" 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" + """The protocol method name (`initialize`).""" + params: InitializeRequestParams + """The initialization parameters (required in every protocol version that + has this request).""" 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 response. + + 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.""" + """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. + """ capabilities: ServerCapabilities + """The capabilities of the server.""" server_info: Implementation + """Information about the server implementation.""" instructions: str | None = None - """Instructions describing how to use the server and its features.""" + """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. + """ 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" @@ -341,13 +804,286 @@ 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. + still alive. The receiver must promptly respond, or else may be disconnected. + + 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 (its ``_meta`` must carry the reserved + ``io.modelcontextprotocol/*`` keys). The session layer supplies the + ``clientInfo``/``clientCapabilities`` entries; the wire boundary + materializes ``params``, injects only ``protocolVersion``, and refuses to + serialize the request without the other two. + """ + + +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. +# Alternative considered: a `mcp/extensions/tasks/` package carrying the extension's task types would attach 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.""" @@ -384,8 +1120,29 @@ class ListResourcesRequest(PaginatedRequest[Literal["resources/list"]]): class Annotations(MCPModel): + """Optional annotations for the client. + + The client can use annotations to inform how objects are used or displayed. + Carried as the optional ``annotations`` field of resources, resource + templates, and content blocks in every protocol version (on 2024-11-05 the + same object is carried anonymously via the schema's ``Annotated`` base + interface). + """ + 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"]``). + """ + priority: Annotated[float, Field(ge=0.0, le=1.0)] | None = 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 Resource(BaseMetadata): @@ -395,7 +1152,11 @@ class Resource(BaseMetadata): """The URI of this resource.""" description: str | None = None - """A description of what this resource represents.""" + """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: str | None = None """The MIME type of this resource, if known.""" @@ -407,15 +1168,13 @@ class Resource(BaseMetadata): """ icons: list[Icon] | None = None - """An optional list of icons for this resource.""" + """Optional set of sized icons that the client can display in a user interface.""" annotations: Annotations | None = None + """Optional annotations for the client.""" 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. - """ + """See the MCP specification for notes on `_meta` usage.""" class ResourceTemplate(BaseMetadata): @@ -425,7 +1184,11 @@ class ResourceTemplate(BaseMetadata): """A URI template (according to RFC 6570) that can be used to construct resource URIs.""" description: str | None = None - """A human-readable description of what this template is for.""" + """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: str | None = None """The MIME type for all resources that match this template. @@ -434,9 +1197,10 @@ class ResourceTemplate(BaseMetadata): """ icons: list[Icon] | None = None - """An optional list of icons for this resource template.""" + """An optional set of sized icons that the client can display in a user interface.""" annotations: Annotations | None = None + """Optional annotations for the client.""" meta: Meta | None = Field(alias="_meta", default=None) """ @@ -445,10 +1209,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,19 +1222,40 @@ 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] + 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. + + 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(RequestParams): - """Parameters for reading a resource.""" +class ReadResourceRequestParams(InputResponseRequestParams): + """Parameters for a `resources/read` request.""" 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. + The URI of the resource. The URI can use any protocol; it is up to the server + how to interpret it. """ @@ -511,10 +1297,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( @@ -522,6 +1309,11 @@ class ResourceListChangedNotification( ): """An optional notification from the server to the client, informing it that the list of resources it can read from has changed. + + On protocol versions up to 2025-11-25, servers may send this spontaneously, + without any previous subscription from the client. On 2026-07-28 sessions, + delivery is opt-in: the server must not send it unless the client requested it + via SubscriptionFilter.resources_list_changed on a subscriptions/listen request. """ method: Literal["notifications/resources/list_changed"] = "notifications/resources/list_changed" @@ -529,7 +1321,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 +1336,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,15 +1347,22 @@ class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscr class UnsubscribeRequestParams(RequestParams): - """Parameters for unsubscribing from a resource.""" + """Parameters for a resources/unsubscribe request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ uri: str """The URI of the resource to unsubscribe from.""" class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]]): - """Sent from the client to request cancellation of resources/updated notifications from - the server. + """Sent from the client to request cancellation of resources/updated notifications + from the server. This should follow a previous resources/subscribe request. + + 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" @@ -564,7 +1370,7 @@ class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/un class ResourceUpdatedNotificationParams(NotificationParams): - """Parameters for resource update notifications.""" + """Parameters for a `notifications/resources/updated` notification.""" uri: str """ @@ -578,23 +1384,109 @@ class ResourceUpdatedNotification( ): """A notification from the server to the client, informing it that a resource has changed and may need to be read again. + + On sessions negotiated at 2025-11-25 or earlier, this should only be sent if the + client previously sent a `resources/subscribe` request. On 2026-07-28 sessions, + it is only sent for resources the client opted in to via the + `resourceSubscriptions` field of a `subscriptions/listen` request. """ method: Literal["notifications/resources/updated"] = "notifications/resources/updated" 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. + """ + + # Alternative considered: 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.""" + """Sent from the client to request a list of prompts and prompt templates the server has.""" 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,14 +1509,15 @@ 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): - """Parameters for getting a prompt.""" +class GetPromptRequestParams(InputResponseRequestParams): + """Parameters for a prompts/get request.""" name: str """The name of the prompt or prompt template.""" @@ -640,12 +1533,14 @@ class GetPromptRequest(Request[GetPromptRequestParams, Literal["prompts/get"]]): class TextContent(MCPModel): - """Text content for a message.""" + """Text provided to or from an LLM.""" type: Literal["text"] = "text" + """Content-type discriminator; always "text".""" text: str """The text content of the message.""" annotations: Annotations | None = None + """Optional annotations for the client.""" 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) @@ -654,9 +1549,10 @@ class TextContent(MCPModel): class ImageContent(MCPModel): - """Image content for a message.""" + """An image provided to or from an LLM.""" type: Literal["image"] = "image" + """Discriminator for image content.""" data: str """The base64-encoded image data.""" mime_type: str @@ -665,17 +1561,16 @@ class ImageContent(MCPModel): image types. """ annotations: Annotations | None = None + """Optional annotations for the client.""" 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. - """ + """See the MCP specification's "General fields: _meta" section for notes on _meta usage.""" class AudioContent(MCPModel): - """Audio content for a message.""" + """Audio provided to or from an LLM.""" type: Literal["audio"] = "audio" + """Discriminator identifying this content block as audio.""" data: str """The base64-encoded audio data.""" mime_type: str @@ -684,6 +1579,7 @@ class AudioContent(MCPModel): audio types. """ annotations: Annotations | None = None + """Optional annotations for the client.""" 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) @@ -694,9 +1590,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,48 +1614,76 @@ 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. """ SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent -"""Content block types allowed in sampling messages.""" +"""Content block types allowed in sampling messages. + +This is the widest (2025-11-25 / 2026-07-28) membership. On older sessions only +a subset is legal on the wire (text/image on 2024-11-05; text/image/audio on +2025-03-26 and 2025-06-18); the wire boundary never narrows a value to fit — +emitting tool blocks (or an array of blocks) on 2025-06-18 or earlier raises +UnsupportedAtVersionError, while audio content passes through to older peers +as sent. + +Deprecated (with the rest of the sampling family) as of protocol 2026-07-28 by +SEP-2577; remains in the specification for at least twelve months and stays +fully supported here for all pre-2026-07-28 sessions. +""" SamplingContent: TypeAlias = TextContent | ImageContent | AudioContent """Basic content types for sampling responses (without tool use). @@ -766,6 +1696,7 @@ class SamplingMessage(MCPModel): """Describes a message issued to or received from an LLM API.""" role: Role + """The role of the message sender ("user" or "assistant").""" content: SamplingMessageContentBlock | list[SamplingMessageContentBlock] """ Message content. Can be a single content block or an array of content blocks @@ -792,8 +1723,11 @@ class EmbeddedResource(MCPModel): """ type: Literal["resource"] = "resource" + """Discriminator for embedded resource content blocks.""" resource: TextResourceContents | BlobResourceContents + """The text or binary contents of the embedded resource.""" annotations: Annotations | None = None + """Optional annotations for the client.""" 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) @@ -815,10 +1749,16 @@ class ResourceLink(Resource): class PromptMessage(MCPModel): - """Describes a message returned as part of a prompt.""" + """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. + """ role: Role + """The sender or recipient of this message in the conversation.""" content: ContentBlock + """The message content: text, image, audio, a resource link, or an embedded resource.""" class GetPromptResult(Result): @@ -827,6 +1767,7 @@ class GetPromptResult(Result): description: str | None = None """An optional description for the prompt.""" messages: list[PromptMessage] + """The messages composing the prompt, in the order they should be presented.""" class PromptListChangedNotification( @@ -834,6 +1775,12 @@ class PromptListChangedNotification( ): """An optional notification from the server to the client, informing it that the list of prompts it offers has changed. + + On sessions negotiated at 2025-11-25 or earlier, servers may send this + spontaneously, without any previous subscription from the client. On + 2026-07-28 sessions delivery is opt-in: the server MUST NOT send it unless + the client requested it via ``subscriptions/listen`` + (``SubscriptionFilter.prompts_list_changed``). """ method: Literal["notifications/prompts/list_changed"] = "notifications/prompts/list_changed" @@ -896,36 +1843,74 @@ class Tool(BaseMetadata): """Definition for a tool the client can call.""" description: str | None = None - """A human-readable description of the tool.""" + """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: dict[str, Any] - """A JSON Schema object defining the expected parameters for the tool.""" - output_schema: dict[str, Any] | None = None + """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. On 2026-07-28 sessions any JSON Schema 2020-12 keyword may appear + alongside `type` (composition, conditional, and reference keywords included); + earlier protocol versions define only `type`, `properties`, and `required` + (plus `$schema` from 2025-11-25). Defaults to JSON Schema 2020-12 when no + explicit `$schema` is provided. + """ + 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). """ - An optional JSON Schema object defining the structure of the tool's output + output_schema: dict[str, Any] | None = None + """An optional JSON Schema object defining the structure of the tool's output returned in the structured_content field of a CallToolResult. + + Restricted to `type: "object"` at the root on 2025-06-18 and 2025-11-25 + sessions; any valid JSON Schema 2020-12 on 2026-07-28. Defaults to JSON + Schema 2020-12 when no explicit `$schema` is provided. """ icons: list[Icon] | None = None - """An optional list of icons for this tool.""" + """Optional set of sized icons that the client can display in a user + interface (2025-11-25 and later).""" annotations: ToolAnnotations | None = None - """Optional additional tool information.""" - 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 additional tool information. + + Display name precedence order is: title, annotations.title, then name. """ + meta: Meta | None = Field(alias="_meta", default=None) + """See the MCP specification's general-fields documentation for notes on + _meta usage.""" -class ListToolsResult(PaginatedResult): +class ListToolsResult(PaginatedResult, CacheableResult): """The server's response to a tools/list request from the client.""" tools: list[Tool] -class CallToolRequestParams(RequestParams): - """Parameters for calling a tool.""" +class CallToolRequestParams(InputResponseRequestParams): + """Parameters for a `tools/call` request.""" name: str + """The name of the tool.""" + arguments: dict[str, Any] | None = None + """Arguments to use for the tool call.""" + + 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,17 +1921,43 @@ 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.""" + # Alternative considered: an Unset-sentinel default distinguishing wire-absent from explicit null. + 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"]]): """An optional notification from the server to the client, informing it that the list of tools it offers has changed. + + On protocol versions through 2025-11-25, servers may send this without any previous + subscription from the client. On 2026-07-28 sessions, delivery is opt-in: the server + sends it only if the client requested it via `subscriptions/listen` + (`SubscriptionFilter.tools_list_changed`). """ method: Literal["notifications/tools/list_changed"] = "notifications/tools/list_changed" @@ -954,24 +1965,50 @@ class ToolListChangedNotification(Notification[NotificationParams | None, Litera LoggingLevel = Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"] +"""The severity of a log message. + +These map to syslog message severities, as specified in RFC-5424: +https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1 + +The value set is identical in every protocol version (2024-11-05 through 2026-07-28). +Protocol 2026-07-28 deprecates the logging family as a whole (SEP-2577) but keeps it +fully functional for at least twelve months; the level scale itself is unchanged. +""" 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.""" + """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(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 class LoggingMessageNotificationParams(NotificationParams): - """Parameters for logging message notifications.""" + """Parameters for a `notifications/message` notification. + + Deprecated as of protocol 2026-07-28 (SEP-2577) but still part of that + version; fully supported on all earlier protocol versions. + """ level: LoggingLevel """The severity of this log message.""" @@ -985,24 +2022,61 @@ class LoggingMessageNotificationParams(NotificationParams): class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, Literal["notifications/message"]]): - """Notification of a log message passed from server to client.""" + """Notification of a log message passed from server to client. + + On protocol versions through 2025-11-25, the client subscribes via + `logging/setLevel`; if it never did, the server MAY decide which messages to + send automatically. On 2026-07-28 sessions the client instead opts in + per-request via the `io.modelcontextprotocol/logLevel` `_meta` key, and the + server MUST NOT send this notification for a request without it (a + session-layer obligation; this type does not validate the send condition). + Deprecated as of protocol 2026-07-28 (SEP-2577) but still part of that version. + """ method: Literal["notifications/message"] = "notifications/message" params: LoggingMessageNotificationParams IncludeContext = Literal["none", "thisServer", "allServers"] +"""Scope of MCP-server context a sampling request asks the client to attach. + +The "thisServer" and "allServers" values are deprecated as of protocol +2025-11-25 (SEP-2596); servers SHOULD omit the field or use "none" unless the +client declares the sampling.context capability. +""" class ModelHint(MCPModel): - """Hints to use for model selection.""" + """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. + + Deprecated as of protocol 2026-07-28 (SEP-2577) together with the rest of + the sampling family; remains in the specification for at least twelve + months and is still carried by embedded sampling requests on 2026-07-28 + sessions. + """ name: str | None = None - """A hint for a model name.""" + """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(MCPModel): - """The server's preferences for model selection, requested by the client during + """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 @@ -1014,6 +2088,10 @@ class ModelPreferences(MCPModel): 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. + + Deprecated as of protocol 2026-07-28 (SEP-2577), along with the rest of the + sampling family, but remains in the specification for at least twelve months + and stays fully supported here. """ hints: list[ModelHint] | None = None @@ -1050,63 +2128,112 @@ class ModelPreferences(MCPModel): class ToolChoice(MCPModel): - """Controls tool usage behavior during sampling. + """Controls tool selection behavior for sampling requests. - Allows the server to specify whether and how the LLM should use tools - in its response. + Sent by servers as `CreateMessageRequestParams.tool_choice` + (sampling-with-tools, protocol 2025-11-25 and later). The client MUST + return an error if it receives the carrying field without having declared + `ClientCapabilities.sampling.tools`. When the carrying field is absent, + the default is `{"mode": "auto"}`. """ mode: Literal["auto", "required", "none"] | None = None """ - Controls when tools are used: + 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 should not use tools + - "none": Model MUST NOT use any tools """ class CreateMessageRequestParams(RequestParams): - """Parameters for creating a message.""" + """Parameters for a sampling/createMessage request.""" messages: list[SamplingMessage] + """The conversation to sample from.""" model_preferences: ModelPreferences | None = None """ The server's preferences for which model to select. The client MAY ignore these preferences. """ system_prompt: str | None = None - """An optional system prompt the server wants to use for sampling.""" + """ + An optional system prompt the server wants to use for sampling. The client + MAY modify or omit this prompt. + """ include_context: IncludeContext | None = None """ - A request to include context from one or more MCP servers (including the caller), to - be attached to the prompt. + 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 "thisServer" and "allServers" values are deprecated + (SEP-2596): servers SHOULD only send them if the client declares the + sampling.context capability. """ temperature: float | None = None + """Sampling temperature requested by the server.""" max_tokens: int - """The maximum number of tokens to sample, as requested by the server.""" + """ + The requested maximum number of tokens to sample (to prevent runaway + completions). The client MAY choose to sample fewer tokens than the + requested maximum. + """ stop_sequences: list[str] | None = None + """Sequences at which the client should stop sampling.""" metadata: dict[str, Any] | None = None - """Optional metadata to pass through to the LLM provider.""" + """ + Optional metadata to pass through to the LLM provider. The format of this + metadata is provider-specific. + """ tools: list[Tool] | None = None """ - Tool definitions for the LLM to use during sampling. - Requires clientCapabilities.sampling.tools to be present. + Tools that the model may use during generation (protocol 2025-11-25 and + later). The client MUST return an error if this field is provided but the + sampling.tools client capability is not declared. """ tool_choice: ToolChoice | None = None """ - Controls tool usage behavior. - Requires clientCapabilities.sampling.tools and the tools parameter to be present. + Controls how the model uses tools (protocol 2025-11-25 and later). The + client MUST return an error if this field is provided but the + sampling.tools client capability is not declared. Default is mode="auto". + """ + 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, and the SDK strips it when emitting to 2026-07-28 peers. """ class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling/createMessage"]]): - """A request from the server to sample an LLM via the client.""" + """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. + + On 2024-11-05 through 2025-11-25 sessions this is a standalone JSON-RPC + server-to-client request. On 2026-07-28 sessions the same payload is + instead embedded in InputRequiredResult.input_requests and is never sent + as a JSON-RPC request. Deprecated as of protocol 2026-07-28 (SEP-2577). + """ method: Literal["sampling/createMessage"] = "sampling/createMessage" params: CreateMessageRequestParams StopReason = Literal["endTurn", "stopSequence", "maxTokens", "toolUse"] | str +"""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 (2025-11-25 and later) + +This is an open string to allow for provider-specific stop reasons; every protocol +version models it as an open union. +""" class CreateMessageResult(Result): @@ -1114,6 +2241,12 @@ class CreateMessageResult(Result): This is the backwards-compatible version that returns single content (no arrays). Used when the request does not include tools. + + The client should inform the user before returning the sampled message, to allow + them to inspect the response (human in the loop). On 2026-07-28 sessions this + payload travels embedded in an ``InputResponses`` map rather than as a top-level + JSON-RPC result; sampling is deprecated as of 2026-07-28 (SEP-2577) but remains + in the specification. """ role: Role @@ -1129,7 +2262,7 @@ class CreateMessageResult(Result): class CreateMessageResultWithTools(Result): """The client's response to a sampling/createMessage request when tools were provided. - This version supports array content for tool use flows. + This version supports array content for tool use flows (2025-11-25 and later). """ role: Role @@ -1158,16 +2291,28 @@ class ResourceTemplateReference(MCPModel): """A reference to a resource or resource template definition.""" type: Literal["ref/resource"] = "ref/resource" + """Reference-type discriminator; always "ref/resource".""" uri: str """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): @@ -1187,10 +2332,12 @@ class CompletionContext(MCPModel): class CompleteRequestParams(RequestParams): - """Parameters for completion requests.""" + """Parameters for a `completion/complete` request.""" ref: ResourceTemplateReference | PromptReference + """The prompt or resource-template reference to complete against.""" argument: CompletionArgument + """The argument's information.""" context: CompletionContext | None = None """Additional, optional context for completions.""" @@ -1223,6 +2370,7 @@ class CompleteResult(Result): """The server's response to a completion/complete request.""" completion: Completion + """The completion values, with optional total / has-more pagination hints.""" class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]): @@ -1233,26 +2381,42 @@ class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]): 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. + + On protocol versions 2024-11-05 through 2025-11-25 this is a server -> client + JSON-RPC request. On 2026-07-28 sessions there are no server -> client JSON-RPC + requests; the same payload travels embedded as an ``InputRequest`` value inside + ``InputRequiredResult.input_requests``. Deprecated as of protocol version + 2026-07-28 (SEP-2577). """ method: Literal["roots/list"] = "roots/list" params: RequestParams | None = None + """Optional request parameters. Unlike client -> server requests, ``params`` + stays optional on 2026-07-28 (the reserved client ``_meta`` keys do not apply + to server -> client payloads).""" class Root(MCPModel): - """Represents a root directory or file that the server can operate on.""" + """Represents a root directory or file that the server can operate on. - uri: FileUrl + Deprecated as of protocol 2026-07-28 (SEP-2577) together with the rest of + the roots family; remains in the specification for at least twelve months + and is still carried by embedded ``roots/list`` responses on 2026-07-28 + sessions. """ - 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. + + uri: FileUrl + """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. """ 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. + """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. """ meta: Meta | None = Field(alias="_meta", default=None) """ @@ -1264,11 +2428,16 @@ class Root(MCPModel): class ListRootsResult(Result): """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. + This result contains an array of Root objects, each representing a root + directory or file that the server can operate on. + + On 2026-07-28 sessions this payload is not a JSON-RPC result: it is carried + as an embedded input-response value (an ``InputResponses`` map entry) on a + retried client request, and the roots feature is deprecated (SEP-2577). """ roots: list[Root] + """The root directories or files the client exposes to the server.""" class RootsListChangedNotification( @@ -1280,6 +2449,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" @@ -1287,21 +2458,34 @@ class RootsListChangedNotification( class CancelledNotificationParams(NotificationParams): - """Parameters for cancellation notifications.""" + """Parameters for a `notifications/cancelled` notification.""" request_id: RequestId | None = None - """ - The ID of the request to cancel. + """The ID of the request to cancel. - This MUST correspond to the ID of a request previously issued in the same direction. + This MUST correspond to the ID of a request previously issued in the same + direction. Required on the wire for protocol versions <= 2025-06-18; + optional from 2025-11-25 (where task cancellation uses the `tasks/cancel` + request, never this field). """ reason: str | None = None - """An optional string describing the reason for the cancellation.""" + """An optional string describing the reason for the cancellation. + + This MAY be logged or presented to the user. + """ class CancelledNotification(Notification[CancelledNotificationParams, Literal["notifications/cancelled"]]): - """This notification can be sent by either side to indicate that it is canceling a - previously-issued request. + """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. + + On protocol versions <= 2025-11-25, a client MUST NOT attempt to cancel its + `initialize` request (the method does not exist at 2026-07-28). """ method: Literal["notifications/cancelled"] = "notifications/cancelled" @@ -1325,39 +2509,22 @@ class ElicitCompleteNotification( URLElicitationRequiredError, update the user interface, or otherwise continue an interaction. However, because delivery of the notification is not guaranteed, clients must not wait indefinitely for a notification from the server. + + New in protocol 2025-11-25, with URL mode itself; no wire form on earlier + sessions. """ method: Literal["notifications/elicitation/complete"] = "notifications/elicitation/complete" 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.""" +"""Schema for elicitation requests. + +A restricted subset of JSON Schema: only top-level properties are allowed, +without nesting. +""" class ElicitRequestFormParams(RequestParams): @@ -1379,12 +2546,22 @@ 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. URL mode directs users to external URLs for sensitive out-of-band interactions like OAuth flows, credential collection, or payment processing. + + New in protocol 2025-11-25: a URL-mode elicitation request has no wire form + on earlier sessions, so serialize_for refuses below that version. """ mode: Literal["url"] = "url" @@ -1402,6 +2579,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 @@ -1409,7 +2593,7 @@ class ElicitRequestURLParams(RequestParams): class ElicitRequest(Request[ElicitRequestParams, Literal["elicitation/create"]]): - """A request from the server to elicit information from the client.""" + """A request from the server to elicit additional information from the user via the client.""" method: Literal["elicitation/create"] = "elicitation/create" params: ElicitRequestParams @@ -1436,21 +2620,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). +""" + +# Alternative considered: a method-discriminated adapter (it would reject 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). +""" + +# Alternative considered: a method-discriminated adapter (it would reject 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. +""" + +# Alternative considered: a method-discriminated adapter (it would reject method-less dicts). server_request_adapter = TypeAdapter[ServerRequest](ServerRequest) @@ -1463,13 +2779,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). +""" + +# Alternative considered: a method-discriminated adapter (it would reject method-less dicts). server_notification_adapter = TypeAdapter[ServerNotification](ServerNotification) ServerResult = ( EmptyResult | InitializeResult + | DiscoverResult | CompleteResult | GetPromptResult | ListPromptsResult @@ -1478,5 +2803,14 @@ 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. `InputRequiredResult` is deliberately the last member: +both of its fields are optional, so an earlier position would shadow other +members during union resolution. +""" 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..1832d5e2fa 100644 --- a/src/mcp/types/jsonrpc.py +++ b/src/mcp/types/jsonrpc.py @@ -2,55 +2,181 @@ 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.""" - -# SDK error codes +"""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 — SDK-internal allocations in the JSON-RPC +# implementation-defined server-error range [-32000, -32099]. Not defined by any +# MCP schema version. The spec now allocates into the same range +# (URL_ELICITATION_REQUIRED = -32042 in 2025-11-25; +# MISSING_REQUIRED_CLIENT_CAPABILITY = -32003 and +# UNSUPPORTED_PROTOCOL_VERSION = -32004 in 2026-07-28), so any future SDK-internal +# code must be chosen away from values the spec has allocated or is likely to +# allocate next. CONNECTION_CLOSED = -32000 +"""SDK-only error code: the connection closed before a response arrived. + +Delivered to local request waiters when the session or dispatcher shuts down with +requests still pending; the SDK never emits this code on the wire. +""" + REQUEST_TIMEOUT = -32001 +"""SDK-only error code: a request timed out while waiting for its response. + +Raised locally as ``MCPError(code=REQUEST_TIMEOUT, ...)``. +""" + REQUEST_CANCELLED = -32002 +"""SDK-only error code: an in-flight request was cancelled. + +Written as a JSON-RPC error response when the dispatcher shuts down while the +request is still being handled. +""" # 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 +202,34 @@ 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".""" + + # If the 2025-11-25 optional-id reading is adopted, `id` gains a None default here. id: RequestId | None + """The id of the request this error responds to. + + The member itself is required: ``None`` carries the JSON-RPC 2.0 + ``"id": null`` form (no request id could be determined, e.g. a parse + error), and a frame missing the key entirely does not validate. The + 2025-11-25 and 2026-07-28 schemas can be read as making the member + optional; the SDK keeps the required-but-nullable shape — which JSON-RPC + 2.0 and every earlier schema mandate — until the spec settles that + reading. SDK-generated error responses always set the member. + """ + 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..f50c7fc315 --- /dev/null +++ b/src/mcp/types/v2024_11_05/__init__.py @@ -0,0 +1,1370 @@ +"""Internal wire-shape models for protocol 2024-11-05. Not part of the public API. + +Initially generated from schema/2024-11-05/schema.json @ 6d441518de8a9d5adbab0b10a76a667a63f90665 by +``scripts/update_spec_types.py --src`` (datamodel-code-generator +0.57.0), then hand-validated against the pinned schema. +Maintained as ordinary source: edits are permitted, but +``tests/types/test_version_model_parity.py`` pins every definition against the +generated spec oracle for this version — a drifting edit fails CI. Prefer +fixing the generator pass and re-scaffolding over hand-patching. + +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. + +Models live in this package's ``__init__.py`` so the whole version reads as +one file beside its pinned schema; the package form leaves room for a future +per-family split without import-path churn. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import ConfigDict, Field + +from mcp.types._wire_base import OpenWireModel, WireModel + + +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..0f0c80099c --- /dev/null +++ b/src/mcp/types/v2025_03_26/__init__.py @@ -0,0 +1,1455 @@ +"""Internal wire-shape models for protocol 2025-03-26. Not part of the public API. + +Initially generated from schema/2025-03-26/schema.json @ 6d441518de8a9d5adbab0b10a76a667a63f90665 by +``scripts/update_spec_types.py --src`` (datamodel-code-generator +0.57.0), then hand-validated against the pinned schema. +Maintained as ordinary source: edits are permitted, but +``tests/types/test_version_model_parity.py`` pins every definition against the +generated spec oracle for this version — a drifting edit fails CI. Prefer +fixing the generator pass and re-scaffolding over hand-patching. + +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. + +Models live in this package's ``__init__.py`` so the whole version reads as +one file beside its pinned schema; the package form leaves room for a future +per-family split without import-path churn. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import ConfigDict, Field + +from mcp.types._wire_base import OpenWireModel, WireModel + + +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. + """ + + 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 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 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 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. + """ + + +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 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"]`). + """ + 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.""" + + 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 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 + """ + Optional annotations for the client. + """ + 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 + """ + 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 + """ + 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 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): + 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 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 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 + """ + 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: str + """ + The URI of this resource. + """ + + +class ResourceTemplate(WireModel): + """A template description for resources available on the server.""" + + 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(WireModel): + """Text provided to or from an LLM.""" + + 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.""" + + 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(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 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. + """ + 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(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] + + +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 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 SamplingMessage(WireModel): + """Describes a message issued to or received from an LLM API.""" + + content: TextContent | ImageContent | AudioContent + role: Role + + +ClientResult: TypeAlias = Result | CreateMessageResult | ListRootsResult + + +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 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] + + +ServerRequest: TypeAlias = PingRequest | CreateMessageRequest | ListRootsRequest + + +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..8e3a467cde --- /dev/null +++ b/src/mcp/types/v2025_06_18/__init__.py @@ -0,0 +1,1718 @@ +"""Internal wire-shape models for protocol 2025-06-18. Not part of the public API. + +Initially generated from schema/2025-06-18/schema.json @ 6d441518de8a9d5adbab0b10a76a667a63f90665 by +``scripts/update_spec_types.py --src`` (datamodel-code-generator +0.57.0), then hand-validated against the pinned schema. +Maintained as ordinary source: edits are permitted, but +``tests/types/test_version_model_parity.py`` pins every definition against the +generated spec oracle for this version — a drifting edit fails CI. Prefer +fixing the generator pass and re-scaffolding over hand-patching. + +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. + +Models live in this package's ``__init__.py`` so the whole version reads as +one file beside its pinned schema; the package form leaves room for a future +per-family split without import-path churn. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import ConfigDict, Field + +from mcp.types._wire_base import OpenWireModel, WireModel + + +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 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 BooleanSchema(WireModel): + default: bool | None = None + description: str | None = None + title: str | None = None + type: Literal["boolean"] + + +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. + """ + + 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(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 Context(WireModel): + """Additional, optional context for completions""" + + arguments: dict[str, str] | None = None + """ + Previously-resolved variables in a URI template or prompt. + """ + + +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 + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + completion: Completion + + +Cursor: TypeAlias = str + + +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 parity 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 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, 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 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 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 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: Params7 | None = None + + +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 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 + + +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 + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +class Notification(WireModel): + method: str + params: NotificationParams | None = None + + +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 parity test pins these annotations + # separately. + maximum: int | float | None = None + minimum: int | float | None = None + title: str | None = None + type: Literal["integer", "number"] + + +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 + """ + 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(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 PromptListChangedNotificationParams(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 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 + """ + 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 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): + """See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage.""" + + 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 + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +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.""" + + 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 ResourceListChangedNotificationParams(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 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 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 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 + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +Role: TypeAlias = Literal["assistant", "user"] + + +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 RootsListChangedNotificationParams(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 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. + """ + + 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 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 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 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 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 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 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 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. + """ + + +class ToolListChangedNotificationParams(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 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 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 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 + """ + 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"] + + +EmptyResult: TypeAlias = Result + + +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 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 + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +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 + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + + +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 + """ + See [General fields: `_meta`](/specification/2025-06-18/basic/index#meta) for notes on `_meta` usage. + """ + roots: list[Root] + + +class PingRequestParams(WireModel): + 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(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 + + +PrimitiveSchemaDefinition: TypeAlias = StringSchema | NumberSchema | BooleanSchema | EnumSchema + + +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 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. + """ + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification +) + + +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). + """ + + +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(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 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 + + +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 + """ + 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. + """ + + +ClientResult: TypeAlias = Result | CreateMessageResult | ListRootsResult | ElicitResult + + +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] + + +ServerRequest: TypeAlias = PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest + + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) 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..902cd4a7b6 --- /dev/null +++ b/src/mcp/types/v2025_11_25/__init__.py @@ -0,0 +1,2857 @@ +"""Internal wire-shape models for protocol 2025-11-25. Not part of the public API. + +Initially generated from schema/2025-11-25/schema.json @ 6d441518de8a9d5adbab0b10a76a667a63f90665 by +``scripts/update_spec_types.py --src`` (datamodel-code-generator +0.57.0), then hand-validated against the pinned schema. +Maintained as ordinary source: edits are permitted, but +``tests/types/test_version_model_parity.py`` pins every definition against the +generated spec oracle for this version — a drifting edit fails CI. Prefer +fixing the generator pass and re-scaffolding over hand-patching. + +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. + +Models live in this package's ``__init__.py`` so the whole version reads as +one file beside its pinned schema; the package form leaves room for a future +per-family split without import-path churn. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import ConfigDict, Field + +from mcp.types._wire_base import OpenWireModel, WireModel + + +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 BlobResourceContents(WireModel): + 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: 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 BooleanSchema(WireModel): + default: bool | None = None + description: str | None = None + title: str | None = None + type: Literal["boolean"] + + +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 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 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 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 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 Context(WireModel): + """Additional, optional context for completions""" + + arguments: dict[str, str] | None = None + """ + Previously-resolved variables in a URI template or prompt. + """ + + +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 + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + completion: Completion + + +Cursor: TypeAlias = str + + +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 parity 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 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 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 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 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 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"] + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +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 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 Notification(WireModel): + method: str + params: dict[str, Any] | None = None + + +class NotificationParams(WireModel): + 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(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 parity 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 PaginatedResult(WireModel): + 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(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 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 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 Meta(OpenWireModel): + """See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.""" + + 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(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 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 Request(WireModel): + method: str + params: dict[str, Any] | None = None + + +RequestId: TypeAlias = str | int + + +class RequestParams(WireModel): + """Common params for any 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. + """ + + +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-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: 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 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 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 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 Result(WireModel): + 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(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-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: 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 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 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): + """Task support for tool-related requests.""" + + call: dict[str, Any] | None = None + """ + Whether the server supports task-augmented tools/call requests. + """ + + +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 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 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. + """ + + +TaskStatus: TypeAlias = Literal["cancelled", "completed", "failed", "input_required", "working"] + + +class TextResourceContents(WireModel): + 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: str + """ + The URI of this resource. + """ + + +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 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 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 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(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 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[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(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 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 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 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(WireModel): + """Audio provided to or from an LLM.""" + + 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: 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 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 CancelTaskRequest(WireModel): + """A request to cancel a task.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/cancel"] + params: CancelTaskRequestParams + + +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 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 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-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(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 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 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-11-25/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 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 JSONRPCErrorResponse(WireModel): + """A response to a request that indicates an error occurred.""" + + error: Error + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class JSONRPCRequest(WireModel): + """A request that expects a response.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class JSONRPCResultResponse(WireModel): + """A successful (non-error) response to a request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +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 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-11-25/basic/index#meta) for notes on `_meta` usage. + """ + roots: list[Root] + + +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 + + +MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema + + +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 + + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +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 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-11-25/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-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 + + +SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema + + +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 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 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-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(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 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 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 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 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 + + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + + +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 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.""" + + 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(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 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 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 + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + + +JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse + + +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 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 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 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"] + + +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. + """ + + +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(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] + + +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(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 + + +ClientResult: TypeAlias = ( + Result + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CreateMessageResult + | ListRootsResult + | ElicitResult +) + + +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 + + +ServerRequest: TypeAlias = ( + PingRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | CreateMessageRequest + | ListRootsRequest + | ElicitRequest +) 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..133c055b60 --- /dev/null +++ b/src/mcp/types/v2026_07_28/__init__.py @@ -0,0 +1,2893 @@ +"""Internal wire-shape models for protocol 2026-07-28. Not part of the public API. + +Initially generated from schema/draft/schema.json @ 6d441518de8a9d5adbab0b10a76a667a63f90665 by +``scripts/update_spec_types.py --src`` (datamodel-code-generator +0.57.0), then hand-validated against the pinned schema. +Maintained as ordinary source: edits are permitted, but +``tests/types/test_version_model_parity.py`` pins every definition against the +generated spec oracle for this version — a drifting edit fails CI. Prefer +fixing the generator pass and re-scaffolding over hand-patching. + +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. + +At this revision the ``clientInfo``/``clientCapabilities`` values inside a +request's ``_meta`` validate through typed models, but vendor extra keys +nested inside those two values pass through emission: the reserved keys and +their values are caller data, and the boundary injects only +``protocolVersion`` (its keys-only alignment restores what this package's +re-dump drops). + +Models live in this package's ``__init__.py`` so the whole version reads as +one file beside its pinned schema; the package form leaves room for a future +per-family split without import-path churn. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import ConfigDict, Field +from typing_extensions import TypeAliasType + +from mcp.types._wire_base import OpenWireModel, WireModel + +# 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 parity test pins this alias separately. +JSONValue = TypeAliasType("JSONValue", "JSONObject | list[JSONValue] | str | int | float | bool | None") + + +JSONObject = TypeAliasType("JSONObject", dict[str, "JSONValue"]) + + +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 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 Context(WireModel): + """Additional, optional context for completions""" + + arguments: dict[str, str] | None = None + """ + Previously-resolved variables in a URI template or prompt. + """ + + +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. + """ + + +Cursor: TypeAlias = str + + +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 parity 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 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 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 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 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(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 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 + """ + The version of this implementation. + """ + website_url: Annotated[str | None, Field(alias="websiteUrl")] = None + """ + An optional URL of the website for this implementation. + """ + + +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 JSONRPCNotification(WireModel): + """A notification which does not expect a response.""" + + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +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"] + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +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 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 Notification(WireModel): + method: str + params: dict[str, Any] | None = None + + +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 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. + """ + + +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 + """ + 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(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 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 Request(WireModel): + method: str + params: dict[str, Any] | None = None + + +RequestId: TypeAlias = str | int + + +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 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 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"`. + """ + + +ResultType: TypeAlias = str + + +Role: TypeAlias = Literal["assistant", "user"] + + +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 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 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 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 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 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 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 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 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(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 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 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 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 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 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(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 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 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. + """ + + +ClientResult: TypeAlias = Result + + +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 CompleteResultResponse(WireModel): + """A successful response from the server for a CompleteRequestcompletion/complete request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: CompleteResult + + +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"] + + +EmptyResult: TypeAlias = Result + + +EnumSchema: TypeAlias = ( + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +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 JSONRPCErrorResponse(WireModel): + """A response to a request that indicates an error occurred.""" + + error: Error + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class JSONRPCRequest(WireModel): + """A request that expects a response.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +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. + """ + + +MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema + + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +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 + + +SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema + + +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 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 + + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + + +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. + """ + + +ElicitRequestParams: TypeAlias = ElicitRequestFormParams | ElicitRequestURLParams + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + + +JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse + + +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 ListPromptsResultResponse(WireModel): + """A successful response from the server for a ListPromptsRequestprompts/list request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListPromptsResult + + +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 ListResourceTemplatesResultResponse(WireModel): + """A successful response from the server for a ListResourceTemplatesRequestresources/templates/list request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourceTemplatesResult + + +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 ListResourcesResultResponse(WireModel): + """A successful response from the server for a ListResourcesRequestresources/list request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourcesResult + + +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 ListToolsResultResponse(WireModel): + """A successful response from the server for a ListToolsRequesttools/list request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListToolsResult + + +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 + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | SubscriptionsAcknowledgedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification + | ElicitationCompleteNotification +) + + +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. + """ + + +ClientNotification: TypeAlias = CancelledNotification | ProgressNotification + + +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"`. + """ + + +SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent + + +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. + """ + + +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(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 CallToolResultResponse(WireModel): + """A successful response from the server for a CallToolRequesttools/call request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | CallToolResult + + +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 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 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 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 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 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 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 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 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. + """ + + +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. + """ + + +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() diff --git a/src/mcp/types/wire.py b/src/mcp/types/wire.py new file mode 100644 index 0000000000..1a3c76a257 --- /dev/null +++ b/src/mcp/types/wire.py @@ -0,0 +1,736 @@ +"""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. + """ + # Alternative considered: narrowing 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. + # Alternative considered: injecting nothing and requiring 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 — the arms + matching the payload's keys are tried best match first and the first that + validates wins — so the open-shaped ``EmptyResult`` arm cannot mask a + better-matching member's validation failures, and a body is rejected only + when every matching arm rejects it (with the best-matching arm's errors); + 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 = _parse_result_union(result_arms, 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 _ranked_result_candidates(arms: tuple[type[Result], ...], data: Mapping[str, Any]) -> list[type[Result]]: + """Rank the result-union members that structurally match the payload. + + 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, ties keeping union declaration order (key counting + cannot tell apart sibling arms with identical key sets, such as the + single-content and array-content sampling results, so candidates are + later validated in rank order rather than committing to one). The ranking + is version-free and ignores unknown fields, so inbound leniency is + untouched. + """ + payload_keys = frozenset(data) + base = len(payload_keys & _wire_field_names(Result)) + matched = [(len(payload_keys & _wire_field_names(arm)), arm) for arm in arms] + return [arm for _, arm in sorted((m for m in matched if m[0] > base), key=lambda m: -m[0])] + + +def _parse_result_union(arms: tuple[type[Result], ...], data: Mapping[str, Any]) -> Any: + """Validate ``data`` against the ranked candidate arms; first success wins. + + When every candidate fails, the reject surfaces the best-ranked + candidate's errors (through the unknown-content-tag refinement), exactly + as if that arm had been validated alone; the all-fail case never falls + back to ``EmptyResult``. With no candidate at all — no arm beats the base + ``Result`` key set — the body parses as the ``EmptyResult`` arm. + """ + candidates = _ranked_result_candidates(arms, data) + if not candidates: + return _validate_refined(EmptyResult, data) + try: + return _adapter(candidates[0]).validate_python(data) + except ValidationError as first_error: + for candidate in candidates[1:]: + try: + return _adapter(candidate).validate_python(data) + except ValidationError: + continue + refined = _refine_unknown_content_type(first_error, data) + if refined is None: + raise + raise refined from first_error + + +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..7e5dace5f6 --- /dev/null +++ b/tests/spec_oracles/PINNED.json @@ -0,0 +1,45 @@ +{ + "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": {} +} 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..703b7ae458 --- /dev/null +++ b/tests/spec_oracles/_harness.py @@ -0,0 +1,670 @@ +"""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). + +Deliberate name divergences are read from `mcp.types._spec_names` — the SDK's +single in-tree record of them — rather than duplicated here: schema defs the +SDK models under a different name resolve through `SDK_TO_SCHEMA_RENAMES`, +schema defs the SDK deliberately does not model (`SCHEMA_NOT_MODELED`) are +skipped, and SDK names with no schema counterpart in any version +(`SDK_ONLY_NAMES`) are exempt from the phantom checks. + +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. +""" + +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 SCHEMA_NOT_MODELED, SDK_ONLY_NAMES, 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", +) + +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"}) +CATEGORIES = frozenset({"not-yet-implemented", "deliberate-deviation", "suspected-sdk-bug"}) + +# Spec def name -> SDK attribute name, for deliberate SDK renames. Read from +# the SDK's divergence map (which records the SDK-name -> schema-name +# direction); 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. A schema-side rename across + # versions, not an SDK divergence, so it lives here rather than in the + # divergence map. + "v2024_11_05": {"ResourceReference": "ResourceTemplateReference"}, + "v2025_03_26": {"ResourceReference": "ResourceTemplateReference"}, +} + +# SDK public names exempt from SDK-TYPE-PHANTOM, beyond the divergence map's +# SDK_ONLY_NAMES (named types with no schema counterpart): 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", + # Reserved `_meta` key string constants (spec prose, not $defs). + "PROTOCOL_VERSION_META_KEY", + "CLIENT_INFO_META_KEY", + "CLIENT_CAPABILITIES_META_KEY", + "LOG_LEVEL_META_KEY", + # JSON-RPC version string constant. + "JSONRPC_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 (the schema models the reserved + # keys on RequestMetaObject, which the SDK deliberately does not model + # as a type - see the divergence map). + "RequestParamsMeta", + # Capability sub-models lifted from inline (non-$defs) schema objects. + "PromptsCapability", + "ResourcesCapability", + "RootsCapability", + "ToolsCapability", + "LoggingCapability", + "CompletionsCapability", + "SamplingCapability", + "SamplingContextCapability", + "SamplingToolsCapability", + "ElicitationCapability", + "FormElicitationCapability", + "UrlElicitationCapability", + # Params classes lifted from inline (non-$defs) params objects. + "CancelTaskRequestParams", + "ElicitCompleteNotificationParams", + "GetTaskPayloadRequestParams", + "GetTaskRequestParams", + # Models/aliases lifted from inline (non-$defs) schema objects. + "Completion", # CompleteResult.completion inline object + "IncludeContext", # CreateMessageRequest.params.includeContext inline enum + "StopReason", # CreateMessageResult.stopReason inline string union + } +) + +# The full SDK-TYPE-PHANTOM exemption set. +PHANTOM_EXEMPT: frozenset[str] = SDK_MACHINERY | SDK_ONLY_NAMES + +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.""" + + 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. + + Every entry pairs a category with a free-text reason that must stand on + its own: ``not-yet-implemented`` marks a gap the burn-down will close, + ``deliberate-deviation`` says why the SDK intentionally differs from the + schema, and ``suspected-sdk-bug`` states the schema fact, the SDK behavior + that disagrees with it, and where in the source tree the pending decision + is recorded. + """ + 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: + raise ValueError(f"allowlist entry {entry.id!r}: only hard findings can be allowlisted") + if not entry.reason.strip(): + raise ValueError(f"allowlist entry {entry.id!r}: empty reason") + 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 + + +def _normalize_model_name(name: str, *, sdk: bool) -> str: + """Map SDK model names back to spec names so paired models compare equal.""" + return SDK_TO_SCHEMA_RENAMES.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) + + +def compare_oracle(oracle: str) -> 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: + if def_name in SCHEMA_NOT_MODELED: + continue + 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)) + else: + 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], +) -> 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 + 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: + 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. + + Defs the SDK deliberately does not model are excluded: a not-modeled def + must not silently satisfy an SDK name's pairing. + """ + 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: + if def_name in SCHEMA_NOT_MODELED: + continue + 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 PHANTOM_EXEMPT: + 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() -> 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)) + findings.extend(aggregated_findings()) + return findings + + +@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). An entry + whose id matches no finding is stale (gate fails). + """ + allowed_ids = {e.id for e in entries} + 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.id not in 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, + ) diff --git a/tests/spec_oracles/burndown_allowlist.json b/tests/spec_oracles/burndown_allowlist.json new file mode 100644 index 0000000000..44ef2aecf2 --- /dev/null +++ b/tests/spec_oracles/burndown_allowlist.json @@ -0,0 +1,1144 @@ +{ + "entries": [ + { + "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/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/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/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/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/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/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": "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/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/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/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_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/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/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/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/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/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/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/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/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/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/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/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/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/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": "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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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" + } + ] +} diff --git a/tests/spec_oracles/test_burndown.py b/tests/spec_oracles/test_burndown.py new file mode 100644 index 0000000000..cc85052009 --- /dev/null +++ b/tests/spec_oracles/test_burndown.py @@ -0,0 +1,265 @@ +"""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 json +import sys +from pathlib import Path +from types import GenericAlias, ModuleType +from typing import Any + +import pytest +from pydantic import Base64Str, BaseModel +from typing_extensions import TypeAliasType + +from tests.spec_oracles import _harness as h + + +def _alias(name: str, value: Any) -> TypeAliasType: + """Build an alias object as plain test data (not a module type alias).""" + return TypeAliasType(name, value) + + +@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() + 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) + formatted = _format(new) + 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{formatted}" + ) + + +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 invariants the loader enforces on the committed file. + for entry in entries: + assert entry.oracle in (*h.ORACLE_MODULES, "sdk") + assert entry.reason.strip() + + +# --- 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 _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 _write_allowlist(tmp_path: Path, entries: list[dict[str, Any]]) -> Path: + path = tmp_path / "allowlist.json" + path.write_text(json.dumps({"entries": entries})) + return path + + +@pytest.mark.parametrize( + ("overrides", "match"), + [ + pytest.param({"name": "Bar"}, "does not match its parts", id="id-parts-mismatch"), + pytest.param({"category": "wontfix"}, "unknown category", id="unknown-category"), + pytest.param( + {"id": "v2026_07_28/Foo#TYPE-WIDER", "check": "TYPE-WIDER"}, + "only hard findings", + id="soft-check", + ), + pytest.param({"reason": " "}, "empty reason", id="blank-reason"), + ], +) +def test_load_allowlist_rejects_malformed_entries(tmp_path: Path, overrides: dict[str, Any], match: str) -> None: + path = _write_allowlist(tmp_path, [_raw_entry(**overrides)]) + with pytest.raises(ValueError, match=match): + h.load_allowlist(path) + + +def test_load_allowlist_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) + + +def test_sdk_lookup_returns_none_for_an_unknown_name() -> None: + assert h.sdk_lookup("NoSuchSdkName") is None + + +def test_sig_canonicalizes_the_less_common_annotation_shapes() -> None: + assert h.sig(Base64Str, sdk=False) == ("base64",) + assert h.sig(bytes, sdk=False) == ("opaque", "builtins.bytes") + # A dict generic with no parameters (the oracles' JSONObject spellings + # vary by version, including unparametrized forms). + assert h.sig(GenericAlias(dict, ()), sdk=False) == ("dict", ("any",), ("any",)) + + +def test_sig_marks_a_revisited_alias_name_as_recursive() -> None: + # Two alias objects sharing one name model a self-referential alias + # (the 2026-07-28 schema's JSONValue group): the second visit to the name + # must terminate instead of recursing forever. + inner = _alias("Recursive", str) + outer = _alias("Recursive", inner | int) + assert h.sig(outer, sdk=False) == ("union", frozenset({("recursive", "Recursive"), ("prim", "int")})) + + +def test_sig_collapses_a_union_whose_members_canonicalize_alike() -> None: + str_alias = _alias("StrAlias", str) + assert h.sig(str_alias | str, sdk=False) == ("prim", "str") + + +def test_strip_optional_null_on_a_null_only_union() -> None: + assert h._strip_optional_null(("union", frozenset({("null",)}))) == ("null",) + + +@pytest.mark.parametrize( + ("spec", "sdk", "expected"), + [ + pytest.param(("any",), ("prim", "str"), "sdk_narrower", id="spec-any"), + pytest.param(("lit", frozenset({"a"})), ("lit", frozenset({"a", "b"})), "sdk_wider", id="lit-subset"), + pytest.param(("lit", frozenset({"a", "b"})), ("lit", frozenset({"a"})), "sdk_narrower", id="lit-superset"), + pytest.param(("lit", frozenset({"a"})), ("lit", frozenset({"b"})), "incomparable", id="lit-disjoint"), + pytest.param(("lit", frozenset({"a"})), ("prim", "str"), "sdk_wider", id="lit-vs-base-prim"), + pytest.param(("lit", frozenset({"a"})), ("prim", "int"), "incomparable", id="lit-vs-other-prim"), + pytest.param(("prim", "str"), ("lit", frozenset({"a"})), "sdk_narrower", id="prim-vs-base-lit"), + pytest.param(("prim", "int"), ("lit", frozenset({"a"})), "incomparable", id="prim-vs-other-lit"), + pytest.param(("base64",), ("prim", "str"), "sdk_wider", id="base64-vs-str"), + pytest.param(("base64",), ("prim", "int"), "incomparable", id="base64-vs-other"), + pytest.param(("prim", "str"), ("base64",), "sdk_narrower", id="str-vs-base64"), + pytest.param(("prim", "int"), ("base64",), "incomparable", id="other-vs-base64"), + pytest.param(("prim", "int"), ("prim", "float"), "sdk_wider", id="int-vs-float"), + pytest.param(("prim", "float"), ("prim", "int"), "sdk_narrower", id="float-vs-int"), + pytest.param(("prim", "str"), ("url", "any"), "sdk_narrower", id="str-vs-url"), + ], +) +def test_compat_relates_non_union_signatures(spec: h.Sig, sdk: h.Sig, expected: h.Compat) -> None: + assert h.compat(spec, sdk) == expected + + +def test_compat_union_member_rejected_without_ambiguity_is_narrower() -> None: + # One spec member is accepted (the int literal fits the int prim), the + # other is provably narrowed (float does not fit int) - no member is + # merely incomparable, so the verdict is a clean narrowing. + spec = ("union", frozenset({("prim", "float"), ("lit", frozenset({1}))})) + assert h.compat(spec, ("prim", "int")) == "sdk_narrower" + + +def test_compat_union_member_with_no_relatable_counterpart_is_incomparable() -> None: + spec = ("union", frozenset({("prim", "str"), ("prim", "int")})) + assert h.compat(spec, ("prim", "int")) == "incomparable" + + +def test_compare_oracle_reports_unpaired_defs_and_sdk_only_required_fields( + monkeypatch: pytest.MonkeyPatch, +) -> None: + class UnpairedSpecType(BaseModel): + pass + + class Icon(BaseModel): + # Deliberately lacks the SDK Icon's required `src` field. + pass + + fake = ModuleType("tests.spec_oracles.fake_oracle") + setattr(fake, "SPEC_DEFS", ("UnpairedSpecType", "Icon")) + setattr(fake, "UnpairedSpecType", UnpairedSpecType) + setattr(fake, "Icon", Icon) + monkeypatch.setitem(sys.modules, "tests.spec_oracles.fake_oracle", fake) + + findings = h.compare_oracle("fake_oracle") + + assert [(f.check, f.name, f.field) for f in findings] == [ + ("SDK-REQUIRED-NOT-IN-SPEC", "Icon", "src"), + ("SPEC-TYPE-MISSING", "UnpairedSpecType", None), + ] + + +def test_aggregated_findings_skip_sdk_models_paired_only_to_non_model_defs( + monkeypatch: pytest.MonkeyPatch, +) -> None: + # An SDK model whose only oracle counterpart is a bare alias offers no + # field sets to compare, so the field-phantom check does not apply. + fake = ModuleType("tests.spec_oracles.fake_oracle") + setattr(fake, "AliasDef", str) + monkeypatch.setitem(sys.modules, "tests.spec_oracles.fake_oracle", fake) + monkeypatch.setattr(h, "_sdk_public_names", lambda: ["Implementation"]) + monkeypatch.setattr(h, "_sdk_to_oracle_defs", lambda: {"Implementation": [("fake_oracle", "AliasDef")]}) + + assert h.aggregated_findings() == [] diff --git a/tests/spec_oracles/test_oracles_smoke.py b/tests/spec_oracles/test_oracles_smoke.py new file mode 100644 index 0000000000..cb2bb674a1 --- /dev/null +++ b/tests/spec_oracles/test_oracles_smoke.py @@ -0,0 +1,75 @@ +"""Wire-fidelity smoke tests for the generated spec-oracle modules. + +Each test pins one schema behavior the oracles must reproduce before they can +serve as the burn-down's comparison baseline: `_meta` reserved-key +round-trips, content-union discrimination, the per-version `resultType` split, +the 2026-07-28 initialize→discover swap, and extra-field retention. +""" + +from __future__ import annotations + +import pytest +from pydantic import TypeAdapter, ValidationError + +from tests.spec_oracles import 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_2026_07_28_call_tool_request_meta_round_trip() -> 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_2026_07_28_content_union_discriminates() -> 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_2026_07_28_result_requires_result_type() -> 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_2026_07_28_drops_initialize_and_adds_discover() -> 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_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..a0b8f27a45 --- /dev/null +++ b/tests/types/test_import_budget.py @@ -0,0 +1,68 @@ +"""Pin the lazy-loading behavior of the per-version model packages. + +The wire boundary (``mcp.types.wire``) validates payloads through the +committed per-version model packages (``mcp.types.v2024_11_05`` ... +``mcp.types.v2026_07_28``), each of which defines on the order of a hundred +pydantic models. Building those classes is the dominant cost of loading the +packages, so the boundary imports a version package only on first use and +``mcp.types.__init__`` does not import the boundary module at all: programs +that never touch the wire boundary never pay for it. + +These tests run a fresh interpreter per case because the test process has +already imported ``mcp`` (a subprocess is the only way to observe a clean +``sys.modules``). +""" + +from __future__ import annotations + +import json +import re +import subprocess +import sys + +import pytest + +from mcp.types import wire + +# Matches exactly the per-version model packages (mcp.types.v2024_11_05, +# ..., mcp.types.v2026_07_28) and no other mcp.types submodule. +_LAZY_MODULE_PATTERN = r"^mcp\.types\.v\d{4}_\d{2}_\d{2}$" + +_REPORT_LAZY_MODULES = """\ +import json +import re +import sys + +{import_statement} + +pattern = re.compile({pattern!r}) +loaded = sorted( + name for name in sys.modules if name == "mcp.types.wire" or pattern.match(name) +) +print(json.dumps(loaded)) +""" + + +@pytest.mark.parametrize("import_statement", ["import mcp", "import mcp.types"]) +def test_version_packages_and_wire_module_are_not_imported_eagerly(import_statement: str) -> None: + """No version package (and not the boundary module) loads as an import side effect.""" + script = _REPORT_LAZY_MODULES.format(import_statement=import_statement, pattern=_LAZY_MODULE_PATTERN) + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + timeout=60, + ) + assert result.returncode == 0, result.stderr + assert json.loads(result.stdout) == [] + + +def test_version_package_names_match_the_lazy_module_pattern() -> None: + """The pattern the subprocess checks really names the version packages. + + Guards the subprocess assertion against going vacuous if the packages + are ever renamed: every module the boundary loads lazily must match the + pattern, so a rename shows up here instead of silently passing above. + """ + for module_name in wire._VERSION_MODULES.values(): + assert re.match(_LAZY_MODULE_PATTERN, module_name), module_name diff --git a/tests/types/test_public_surface.py b/tests/types/test_public_surface.py new file mode 100644 index 0000000000..f5a6e72908 --- /dev/null +++ b/tests/types/test_public_surface.py @@ -0,0 +1,374 @@ +"""Pin the public type surface against the fork-point baseline. + +``mcp.types.__all__`` is a one-way compatibility ratchet: every name the +module exported at the fork point is still exported (zero removals), and this +branch adds exactly the names in ``_ADDED_EXPORTS`` — nothing else. The +curated top-level surface (``mcp.__all__``) gains nothing. Negotiation +defaults are pinned unchanged: modeling the 2026-07-28 protocol revision must +not change which protocol versions the SDK advertises or negotiates. +""" + +from __future__ import annotations + +from typing import Any + +import mcp +import mcp.types +from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS +from mcp.types import CallToolRequestParams, Tool + +_BASELINE_EXPORTS: tuple[str, ...] = ( + # `mcp.types.__all__` at the fork point (153 names, sorted). Removing any + # of these is a breaking change; this tuple is never edited, only the + # additions tuple below grows. + "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", +) + +_ADDED_EXPORTS: tuple[str, ...] = ( + # Everything this branch adds to `mcp.types.__all__` (43 names). Grouped + # by the protocol feature that introduces each name. + # + # JSON-RPC protocol identifier, re-exported from mcp.types.jsonrpc. + "JSONRPC_VERSION", + # Result completion state, added in 2026-07-28 (absent means complete). + "ResultType", + # Client-side caching directives on results, added in 2026-07-28. + "CacheableResult", + # The server/discover lifecycle request, added in 2026-07-28. + "DiscoverRequest", + "DiscoverResult", + # Filtered resource subscriptions, added in 2026-07-28. + "SubscriptionFilter", + "SubscriptionsAcknowledgedNotification", + "SubscriptionsAcknowledgedNotificationParams", + "SubscriptionsListenRequest", + "SubscriptionsListenRequestParams", + # Server-initiated input requests during sampling, added in 2026-07-28. + "InputRequest", + "InputRequests", + "InputRequiredResult", + "InputResponse", + "InputResponseRequestParams", + "InputResponses", + # Error payloads and codes, added in 2026-07-28. + "MISSING_REQUIRED_CLIENT_CAPABILITY", + "MissingRequiredClientCapabilityErrorData", + "UNSUPPORTED_PROTOCOL_VERSION", + "UnsupportedProtocolVersionErrorData", + # Reserved `_meta` key names, added in 2026-07-28. + "CLIENT_CAPABILITIES_META_KEY", + "CLIENT_INFO_META_KEY", + "LOG_LEVEL_META_KEY", + "PROTOCOL_VERSION_META_KEY", + # Task types from 2025-11-25, modeled again so that sessions negotiating + # that version can exchange them (the methods were removed in 2026-07-28). + "CancelTaskRequest", + "CancelTaskRequestParams", + "CancelTaskResult", + "CreateTaskResult", + "GetTaskPayloadRequest", + "GetTaskPayloadRequestParams", + "GetTaskPayloadResult", + "GetTaskRequest", + "GetTaskRequestParams", + "GetTaskResult", + "ListTasksRequest", + "ListTasksResult", + "RelatedTaskMetadata", + "Task", + "TaskMetadata", + "TaskStatus", + "TaskStatusNotification", + "TaskStatusNotificationParams", + "ToolExecution", +) + +_TOP_LEVEL_EXPORTS: tuple[str, ...] = ( + # `mcp.__all__` at the fork point (66 names, original order). This branch + # adds nothing to the curated top-level surface. + "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", + "ResourcesCapability", + "ResourceUpdatedNotification", + "RootsCapability", + "SamplingCapability", + "SamplingContent", + "SamplingContextCapability", + "SamplingMessage", + "SamplingMessageContentBlock", + "SamplingRole", + "SamplingToolsCapability", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "ServerSession", + "SetLevelRequest", + "StdioServerParameters", + "StopReason", + "SubscribeRequest", + "Tool", + "ToolChoice", + "ToolResultContent", + "ToolsCapability", + "ToolUseContent", + "UnsubscribeRequest", + "UrlElicitationRequiredError", + "stdio_client", + "stdio_server", +) + + +def test_pinned_lists_are_internally_consistent() -> None: + """Self-check on the pinned data: counts, no duplicates, no overlap.""" + assert len(_BASELINE_EXPORTS) == 153 + assert len(set(_BASELINE_EXPORTS)) == 153 + assert len(_ADDED_EXPORTS) == 43 + assert len(set(_ADDED_EXPORTS)) == 43 + assert set(_BASELINE_EXPORTS) & set(_ADDED_EXPORTS) == set() + assert len(_TOP_LEVEL_EXPORTS) == 66 + + +def test_types_exports_are_baseline_plus_exactly_the_additions() -> None: + """`mcp.types.__all__` keeps every fork-point name and adds only the pinned names.""" + exported = set(mcp.types.__all__) + removed = set(_BASELINE_EXPORTS) - exported + assert removed == set(), f"fork-point exports must never be removed: {sorted(removed)}" + assert exported - set(_BASELINE_EXPORTS) == set(_ADDED_EXPORTS) + + +def test_types_export_list_has_no_duplicates() -> None: + """`mcp.types.__all__` lists each name exactly once.""" + assert len(mcp.types.__all__) == len(set(mcp.types.__all__)) + + +def test_every_types_export_resolves() -> None: + """Every name in `mcp.types.__all__` is an attribute of the module.""" + missing = [name for name in mcp.types.__all__ if not hasattr(mcp.types, name)] + assert missing == [] + + +def test_top_level_exports_unchanged() -> None: + """`mcp.__all__` is exactly the fork-point list, in the same order.""" + assert list(mcp.__all__) == list(_TOP_LEVEL_EXPORTS) + + +def test_negotiation_defaults_unchanged() -> None: + """The SDK advertises and negotiates the same versions as at the fork point. + + 2026-07-28 types are modeled, but the version is not offered during + negotiation; enabling it is a separate, deliberate change. + """ + assert mcp.types.LATEST_PROTOCOL_VERSION == "2025-11-25" + assert mcp.types.DEFAULT_NEGOTIATED_VERSION == "2025-03-26" + assert SUPPORTED_PROTOCOL_VERSIONS == ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"] + + +def test_wire_name_constructor_kwargs_still_work() -> None: + """Models keep accepting wire-name (camelCase) constructor kwargs. + + The static signature lists only the snake_case field names, so the + wire-name spelling is passed as an unpacked dict; at runtime both + spellings construct the same model. + """ + wire_name_kwargs: dict[str, Any] = {"name": "t", "inputSchema": {"type": "object"}} + assert Tool(**wire_name_kwargs) == Tool(name="t", input_schema={"type": "object"}) + + +def test_meta_constructor_kwargs_still_work() -> None: + """Request params accept both the `_meta` wire alias and the `meta` field name. + + The static signature lists only the `_meta` spelling, so the field-name + spelling is passed as an unpacked dict; at runtime both spellings + construct the same model. + """ + field_name_kwargs: dict[str, Any] = {"name": "t", "meta": {"k": "v"}} + assert CallToolRequestParams(**field_name_kwargs) == CallToolRequestParams(name="t", _meta={"k": "v"}) diff --git a/tests/types/test_version_model_parity.py b/tests/types/test_version_model_parity.py new file mode 100644 index 0000000000..8185f8e65b --- /dev/null +++ b/tests/types/test_version_model_parity.py @@ -0,0 +1,508 @@ +"""Pin every committed version-package definition against its spec oracle. + +The packages under ``src/mcp/types/v*`` are generated-then-hand-validated +source; the oracle modules under ``tests/spec_oracles`` are regenerated +verbatim from the pinned schemas. This test compares the two per version — +definition sets in both directions, and per model the wire aliases, +requiredness, and a normalized form of every field annotation — so a hand +edit that drifts from the pinned schema fails here. + +The deliberate scaffold-pass deltas are the annotated tolerance tables below; +everything else must match exactly. Two deltas need no tolerance entry: +inheritance flattening (pydantic's ``model_fields`` already includes +inherited fields on the oracle side, so flattened package classes compare +equal), and the deterministic synthetic class names (derived from the oracle +in ``_synthetic_renames``, mirroring the scaffold pass). +""" + +from __future__ import annotations + +import importlib +import re +from types import ModuleType, UnionType +from typing import Annotated, Any, Literal, Union, get_args, get_origin + +import pytest +from pydantic import BaseModel, create_model +from typing_extensions import TypeAliasType + +VERSIONS = ( + "v2024_11_05", + "v2025_03_26", + "v2025_06_18", + "v2025_11_25", + "v2026_07_28", +) + +# Tolerance: value-transforming pydantic types are downgraded to plain ``str`` +# in the packages — 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 already compare as ``str``.) +_VALUE_DOWNGRADES = {"AnyUrl": "str", "FileUrl": "str"} + +# Tolerance: the packages 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 package follows the schema.ts +# definition so fractional numbers and nulls validate. The package 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: content blocks carried into packages older than the schema that +# introduces them, so emitting such a block to an older peer passes it through +# instead of refusing. Maps package -> {class name -> introducing oracle}; +# the carried class text is pinned against that oracle in +# test_carried_content_block_matches_introducing_version. +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"}, +} + +# Tolerance: the pinned schema.json renderings type a few schema.ts `number` +# positions as "integer" — the same render artifact fixed for the JSONValue +# alias. The packages 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 generated oracles keep the render verbatim, and the package +# annotation is pinned here verbatim so any further drift still fails. +# Position -> the exact package 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 package 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 name 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", +} + +# The expected-open class policy: every package class is ``extra="ignore"`` +# except the ``_meta`` carriers (unknown ``_meta`` keys must survive +# revalidation), the tool input/output schema interiors (schema keywords +# beyond the declared properties ride extra fields), and the subscription +# filter (extensible on the wire). The first two groups are derived from the +# package's own field references in test_extra_policy. +_OPEN_INTERIOR_ALIASES = frozenset({"inputSchema", "outputSchema"}) + +# Names a module's import block binds; everything else public is a definition. +_IMPORTED_NAMES = frozenset( + { + "annotations", + "Annotated", + "Any", + "Literal", + "TypeAlias", + "TypeAliasType", + "AnyUrl", + "Base64Str", + "ConfigDict", + "Field", + "OracleModel", + "WireModel", + "OpenWireModel", + "SPEC_DEFS", + } +) + +Sig = tuple[Any, ...] + + +def _package(version: str) -> ModuleType: + return importlib.import_module(f"mcp.types.{version}") + + +def _oracle(version: str) -> ModuleType: + return importlib.import_module(f"tests.spec_oracles.{version}") + + +def _module_defs(mod: ModuleType) -> dict[str, Any]: + """Public names a module defines (classes and type aliases).""" + return {name: obj for name, obj in vars(mod).items() if not name.startswith("_") and name not in _IMPORTED_NAMES} + + +def _module_classes(mod: ModuleType) -> dict[str, type[BaseModel]]: + """Model classes a module defines, keyed by class name (aliases excluded).""" + return { + name: obj + for name, obj in vars(mod).items() + if isinstance(obj, type) + and issubclass(obj, BaseModel) + and obj.__module__ == mod.__name__ + and obj.__name__ == name + } + + +def _model_names_in(annotation: Any) -> frozenset[str]: + """Names of model classes appearing anywhere in an annotation.""" + if isinstance(annotation, type) and issubclass(annotation, BaseModel): + return frozenset({annotation.__name__}) + names: set[str] = set() + for arg in get_args(annotation): + names.update(_model_names_in(arg)) + return frozenset(names) + + +def _synthetic_renames(oracle: ModuleType) -> dict[str, str]: + """Oracle synthetic class name -> package class name. + + Mirrors the scaffold's deterministic-naming pass: a generated + ``Params``/``Meta`` class referenced by exactly one field of one + owner is named ``Params``/``Meta`` in the package; shared + synthetics keep their generated names. + """ + classes = _module_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 & _model_names_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 package + 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_classes_match( + oracle_cls: type[BaseModel], + package_cls: type[BaseModel], + *, + rename: dict[str, str], + drop: frozenset[str], + context: str, + overrides: dict[str, Any] | None = None, +) -> None: + """Field-level comparison: names, wire aliases, requiredness, annotations. + + ``overrides`` maps a field name to the verbatim package annotation pinned + for it (the render-artifact widenings); for those fields the package is + compared against the pin instead of the oracle's rendered annotation. + """ + 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" + widen = field_name in _WIDENED_FIELDS + package_sig = _sig(package_info.annotation, rename={}, drop=drop, widen_dicts=widen) + if overrides is not None and field_name in overrides: + pinned_sig = _sig(overrides[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" + + +@pytest.mark.parametrize("version", VERSIONS) +def test_definition_sets_match(version: str) -> None: + """Both directions: every oracle definition is in the package and vice versa.""" + oracle_defs = _module_defs(_oracle(version)) + package_defs = _module_defs(_package(version)) + rename = _synthetic_renames(_oracle(version)) + expected = {rename.get(name, name) for name in oracle_defs} + carried = set(CARRIED_CONTENT_BLOCKS.get(version, {})) + missing = expected - set(package_defs) + assert not missing, f"{version}: oracle definitions missing from the package: {sorted(missing)}" + extra = set(package_defs) - expected - carried + assert not extra, f"{version}: package definitions with no oracle counterpart: {sorted(extra)}" + + +@pytest.mark.parametrize("version", VERSIONS) +def test_model_fields_match(version: str) -> None: + """Every shared model class matches its oracle field for field.""" + oracle = _oracle(version) + package = _package(version) + rename = {**_synthetic_renames(oracle), **_VALUE_DOWNGRADES} + drop = frozenset(CARRIED_CONTENT_BLOCKS.get(version, {})) + package_classes = _module_classes(package) + for oracle_name, oracle_cls in _module_classes(oracle).items(): + package_cls = package_classes[rename.get(oracle_name, oracle_name)] + overrides = { + field_name: annotation + for (widened_version, class_name, field_name), annotation in _RENDER_ARTIFACT_WIDENED.items() + if widened_version == version and class_name == package_cls.__name__ + } + _assert_classes_match( + oracle_cls, + package_cls, + rename=rename, + drop=drop, + context=f"{version}.{package_cls.__name__}", + overrides=overrides, + ) + + +@pytest.mark.parametrize("version", VERSIONS) +def test_alias_definitions_match(version: str) -> None: + """Every non-class definition (type alias) matches its oracle form.""" + oracle = _oracle(version) + package = _package(version) + rename = {**_synthetic_renames(oracle), **_VALUE_DOWNGRADES} + drop = frozenset(CARRIED_CONTENT_BLOCKS.get(version, {})) + oracle_classes = _module_classes(oracle) + package_defs = _module_defs(package) + for name, oracle_obj in _module_defs(oracle).items(): + if name in oracle_classes: + continue + package_obj = package_defs[rename.get(name, name)] + override = _ALIAS_OVERRIDES.get((version, name)) + if override is not None: + assert isinstance(package_obj, TypeAliasType), f"{version}.{name}: overridden alias must stay lazy" + assert package_obj.__value__ == override, f"{version}.{name}: alias value differs from its override" + continue + if isinstance(oracle_obj, TypeAliasType): + assert isinstance(package_obj, TypeAliasType), f"{version}.{name}: oracle is a lazy alias" + oracle_sig = _sig(oracle_obj.__value__, rename=rename, drop=frozenset()) + package_sig = _sig(package_obj.__value__, rename={}, drop=drop) + else: + oracle_sig = _sig(oracle_obj, rename=rename, drop=frozenset()) + package_sig = _sig(package_obj, rename={}, drop=drop) + assert package_sig == oracle_sig, f"{version}.{name}: alias value differs" + + +@pytest.mark.parametrize( + ("version", "block", "introducing"), + [ + ("v2024_11_05", "AudioContent", "v2025_03_26"), + ("v2024_11_05", "ResourceLink", "v2025_06_18"), + ("v2025_03_26", "ResourceLink", "v2025_06_18"), + ], +) +def test_carried_content_block_matches_introducing_version(version: str, block: str, introducing: str) -> None: + """A carried content block is byte-faithful to the schema that introduces it.""" + package_cls = _module_classes(_package(version))[block] + oracle_cls = _module_classes(_oracle(introducing))[block] + _assert_classes_match( + oracle_cls, + package_cls, + rename=dict(_VALUE_DOWNGRADES), + drop=frozenset(), + context=f"{version}.{block} (vs {introducing} oracle)", + ) + + +@pytest.mark.parametrize("version", VERSIONS) +def test_extra_policy(version: str) -> None: + """Package classes are closed except the enumerated open classes. + + Open by design: the ``_meta`` carriers (unknown ``_meta`` keys survive + revalidation), the tool input/output schema interiors, and the + subscription filter. Everything else is ``extra="ignore"`` so a field the + target version never defined registers as a loss on revalidation. + """ + classes = _module_classes(_package(version)) + 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 _model_names_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" + + +# --- number-render closure sweep ------------------------------------------------- + + +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", VERSIONS) +def test_int_only_number_positions_are_classified(version: str) -> None: + """Every package field position that admits int but not float is claimed + by exactly one pinned table: the render-artifact widenings (the package + 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.""" + for class_name, cls in _module_classes(_package(version)).items(): + for field_name, info in cls.model_fields.items(): + int_only = _admits_int_without_float(info.annotation) + if (version, class_name, field_name) in _RENDER_ARTIFACT_WIDENED: + assert not int_only, f"{version}.{class_name}.{field_name}: pinned widening lost its float arm" + elif int_only: + assert field_name in _INTENDED_INTEGER_FIELDS, ( + f"{version}.{class_name}.{field_name}: int-without-float position is neither a pinned " + "render-artifact widening nor a pinned intended-integer field" + ) + + +# --- helper unit tests (synthetic data; the canonicalizers must not go vacuous) --- + + +def _fake_oracle(**classes: type[BaseModel]) -> ModuleType: + module = ModuleType("fake_oracle") + for name, cls in classes.items(): + setattr(module, name, cls) + return module + + +# Alias object as test data for the canonicalizer below. +SomeAlias = TypeAliasType("SomeAlias", str) + + +def test_synthetic_rename_requires_a_params_or_meta_field_reference() -> None: + # A synthetic class referenced from a field that is not `params`/`meta` + # derives no owner-based name and keeps its generated one. + params = create_model("Params1", __module__="fake_oracle") + owner = create_model("Owner", __module__="fake_oracle", payload=(params | None, None)) + assert _synthetic_renames(_fake_oracle(Params1=params, Owner=owner)) == {} + + +def test_synthetic_rename_never_collides_with_an_existing_class_name() -> None: + params = create_model("Params1", __module__="fake_oracle") + owner = create_model("Owner", __module__="fake_oracle", params=(params | None, None)) + taken = create_model("OwnerParams", __module__="fake_oracle") + assert _synthetic_renames(_fake_oracle(Params1=params, Owner=owner, OwnerParams=taken)) == {} + + +def test_sig_keeps_alias_references_by_name() -> None: + assert _sig(SomeAlias, rename={}, drop=frozenset()) == ("aliasref", "SomeAlias") + + +def test_sig_unwraps_annotated_metadata() -> None: + assert _sig(Annotated[str, "wire metadata"], rename={}, drop=frozenset()) == ("cls", "str") + + +def test_sig_collapses_a_union_left_with_one_member_after_dropping_arms() -> None: + kept = create_model("KeptArm") + dropped = create_model("DroppedArm") + assert _sig(kept | dropped, rename={}, drop=frozenset({"DroppedArm"})) == ("cls", "KeptArm") + + +# Alias objects as test data for the int-admission walker below. +IntAlias = TypeAliasType("IntAlias", int) + + +def test_int_admission_walker_resolves_aliases() -> None: + assert _admits_int_without_float(IntAlias) + assert not _admits_int_without_float(SomeAlias) + + +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(IntAlias, frozenset({id(IntAlias)})) + + +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: + # Annotated survives only nested inside other annotations (pydantic strips + # it from the top level of model_fields), so the branch is pinned here. + assert _admits_int_without_float(Annotated[int, "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) diff --git a/tests/types/test_version_registry.py b/tests/types/test_version_registry.py new file mode 100644 index 0000000000..cc2a8c6de1 --- /dev/null +++ b/tests/types/test_version_registry.py @@ -0,0 +1,176 @@ +"""Pin the version registry and the per-version method tables. + +The four tables exported by ``mcp.types.wire`` record, for each known +protocol version, which JSON-RPC method strings exist in that version's +schema, split by direction and message kind. They are hand-maintained +literals, so they are pinned three ways here: registry/shape invariants, +per-method anchor facts taken from the published schemas, and full equality +against the method sets derived from the committed version packages and from +the generated spec oracles (equality, not subset — a silently omitted method +must fail). +""" + +from __future__ import annotations + +import importlib +from collections.abc import Mapping +from types import ModuleType +from typing import get_args + +import pytest + +import mcp.shared.version +from mcp.types import DEFAULT_NEGOTIATED_VERSION, LATEST_PROTOCOL_VERSION, wire + +_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, +} + +# The direction union each table corresponds to in the version packages and +# the spec oracles. +_UNION_NAMES: dict[str, str] = { + "client requests": "ClientRequest", + "client notifications": "ClientNotification", + "server requests": "ServerRequest", + "server notifications": "ServerNotification", +} + +_PACKAGES: dict[str, str] = { + "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", +} + +# The 2025-11-25 schema defines four task request methods; the SDK models the +# task types but never dispatches them, so the tables exclude exactly these +# methods (and nothing else) from what the schema unions define. +_EXCLUDED_TASK_REQUEST_METHODS = frozenset({"tasks/get", "tasks/cancel", "tasks/result", "tasks/list"}) + +_RELEASED = ("2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25") +_ALL = _RELEASED + ("2026-07-28",) + + +def test_registry_is_the_shared_version_registry() -> None: + """The boundary re-exports the SDK's single version registry.""" + assert wire.KNOWN_PROTOCOL_VERSIONS is mcp.shared.version.KNOWN_PROTOCOL_VERSIONS + + +def test_registry_lists_known_versions_oldest_to_newest() -> None: + assert wire.KNOWN_PROTOCOL_VERSIONS == _ALL + + +def test_negotiation_constants_are_registered_versions() -> None: + """The negotiation constants name protocol revisions the registry knows.""" + assert LATEST_PROTOCOL_VERSION in wire.KNOWN_PROTOCOL_VERSIONS + assert DEFAULT_NEGOTIATED_VERSION in wire.KNOWN_PROTOCOL_VERSIONS + + +@pytest.mark.parametrize("table_name", sorted(_TABLES)) +def test_tables_are_keyed_by_the_registry_in_order(table_name: str) -> None: + assert tuple(_TABLES[table_name]) == wire.KNOWN_PROTOCOL_VERSIONS + + +# Per-method anchor facts, each row: (method, table, versions where defined). +# Sources: the published schema unions; 2026-07-28 removed the lifecycle +# handshake, logging/setLevel, the resources subscribe pair, the roots +# methods, and the entire server -> client request channel, and added +# server/discover, subscriptions/listen, and +# notifications/subscriptions/acknowledged. +_ANCHORS: list[tuple[str, str, tuple[str, ...]]] = [ + ("initialize", "client requests", _RELEASED), + ("notifications/initialized", "client notifications", _RELEASED), + ("ping", "client requests", _RELEASED), + ("ping", "server requests", _RELEASED), + ("logging/setLevel", "client requests", _RELEASED), + ("resources/subscribe", "client requests", _RELEASED), + ("resources/unsubscribe", "client requests", _RELEASED), + ("server/discover", "client requests", ("2026-07-28",)), + ("subscriptions/listen", "client requests", ("2026-07-28",)), + ("tools/list", "client requests", _ALL), + ("tools/call", "client requests", _ALL), + ("prompts/list", "client requests", _ALL), + ("prompts/get", "client requests", _ALL), + ("resources/list", "client requests", _ALL), + ("resources/templates/list", "client requests", _ALL), + ("resources/read", "client requests", _ALL), + ("completion/complete", "client requests", _ALL), + # elicitation/create entered the schema in 2025-06-18 and left with the + # whole server -> client request channel in 2026-07-28. + ("elicitation/create", "server requests", ("2025-06-18", "2025-11-25")), + ("sampling/createMessage", "server requests", _RELEASED), + ("roots/list", "server requests", _RELEASED), + ("notifications/roots/list_changed", "client notifications", _RELEASED), + ("notifications/elicitation/complete", "server notifications", ("2025-11-25", "2026-07-28")), + ("notifications/subscriptions/acknowledged", "server notifications", ("2026-07-28",)), + # notifications/tasks/status is a 2025-11-25 schema fact in both + # directions; it stays in the tables even though the SDK's notification + # unions exclude its type. + ("notifications/tasks/status", "client notifications", ("2025-11-25",)), + ("notifications/tasks/status", "server notifications", ("2025-11-25",)), + ("notifications/progress", "client notifications", _ALL), + ("notifications/progress", "server notifications", _ALL), + ("notifications/cancelled", "client notifications", _ALL), + ("notifications/cancelled", "server notifications", _ALL), + ("notifications/message", "server notifications", _ALL), +] + + +@pytest.mark.parametrize(("method", "table_name", "versions"), _ANCHORS) +def test_method_membership_anchor(method: str, table_name: str, versions: tuple[str, ...]) -> None: + """A method appears in its table at exactly the versions defining it.""" + table = _TABLES[table_name] + for version in wire.KNOWN_PROTOCOL_VERSIONS: + assert (method in table[version]) == (version in versions), f"{method} at {version}" + + +def test_no_task_request_method_in_any_table() -> None: + """The four 2025-11-25 task request methods are excluded everywhere.""" + for table in _TABLES.values(): + for methods in table.values(): + assert not (methods & _EXCLUDED_TASK_REQUEST_METHODS) + + +def test_no_server_requests_at_2026_07_28() -> None: + """2026-07-28 removed the standalone server -> client request channel.""" + assert wire.SERVER_REQUEST_METHODS["2026-07-28"] == frozenset() + + +def _union_methods(module: ModuleType, union_name: str) -> frozenset[str]: + """The method literals of a direction union's arms. + + Empty when the module does not define the union (the 2026-07-28 schema + has no server -> client requests, so it exports no ServerRequest union). + """ + union = getattr(module, union_name, None) + if union is None: + return frozenset() + methods: set[str] = set() + for arm in get_args(union): + (literal,) = get_args(arm.model_fields["method"].annotation) + methods.add(literal) + return frozenset(methods) + + +@pytest.mark.parametrize("version", _ALL) +@pytest.mark.parametrize("table_name", sorted(_TABLES)) +def test_table_equals_version_package_union_methods(table_name: str, version: str) -> None: + """Each table entry equals the method set derived from the committed + version package's direction union, minus the task request methods.""" + package = importlib.import_module(f"mcp.types.{_PACKAGES[version]}") + derived = _union_methods(package, _UNION_NAMES[table_name]) - _EXCLUDED_TASK_REQUEST_METHODS + assert _TABLES[table_name][version] == derived + + +@pytest.mark.parametrize("version", _ALL) +@pytest.mark.parametrize("table_name", sorted(_TABLES)) +def test_table_equals_spec_oracle_union_methods(table_name: str, version: str) -> None: + """Each table entry equals the method set derived from the generated spec + oracle's direction union, minus the task request methods.""" + oracle = importlib.import_module(f"tests.spec_oracles.{_PACKAGES[version]}") + derived = _union_methods(oracle, _UNION_NAMES[table_name]) - _EXCLUDED_TASK_REQUEST_METHODS + assert _TABLES[table_name][version] == derived diff --git a/tests/types/test_wire_boundary.py b/tests/types/test_wire_boundary.py new file mode 100644 index 0000000000..3752108ff9 --- /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_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 packages 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)) + + +@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)) + + +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..cf9a71299c --- /dev/null +++ b/tests/types/test_wire_parse.py @@ -0,0 +1,579 @@ +"""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, get_args + +import pytest +from pydantic import BaseModel, 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, + client_result_adapter, + 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) + + +# --- identical-key-set sibling arms (ranked candidate trial) ----------------------------- + +# The monolith splits the schema's single sampling result into a single-content +# arm and an array-content arm with the same top-level wire keys, so key-count +# ranking alone cannot order them; routing tries every structurally matching +# arm, best match first, and the first that validates wins. + + +@pytest.mark.parametrize("version", [V1, 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 (spec-mandated shape, superset parse).""" + 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: dict[str, Any] = { + "role": "assistant", + "content": {"type": "text", "text": "sunny"}, + "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 its 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": "probe-server", "version": "0.1.0"}, + "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",) + + +def _wire_keys(cls: type[BaseModel]) -> frozenset[str]: + """A model's top-level wire key set (each field's alias when it has one).""" + return frozenset(info.alias or name for name, info in cls.model_fields.items()) + + +def _tie_groups(union: Any) -> list[list[type[BaseModel]]]: + """Arms of a result union sharing one top-level wire key set, in order.""" + groups: dict[frozenset[str], list[type[BaseModel]]] = {} + for arm in get_args(union): + groups.setdefault(_wire_keys(arm), []).append(arm) + return [arms for arms in groups.values() if len(arms) > 1] + + +def test_tie_groups_resolve_exactly_like_the_plain_union_adapter() -> None: + """Mechanically derive the arms sharing a top-level wire key set in each + public result union: key counting cannot order such siblings, so for + bodies shaped like a tie group's members parse_as must agree with the + plain smart-union adapter's resolution class. Today the only tie group is + the sampling-result split; a new arm joining a tie group fails this pin + and must bring its own routing tests.""" + assert _tie_groups(ServerResult) == [] + assert _tie_groups(ClientResult) == [[CreateMessageResult, CreateMessageResultWithTools]] + single: dict[str, Any] = {"role": "assistant", "content": {"type": "text", "text": "hi"}, "model": "m"} + array: dict[str, Any] = {"role": "assistant", "content": [{"type": "text", "text": "hi"}], "model": "m"} + for body in (single, array): + assert type(parse_as(ClientResult, body, V4)) is type(client_result_adapter.validate_python(body)) + + +# --- 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