Source code for smarter.apps.mcpserver.manifests.tools
"""
The tools that read and validate the platform's schemas.
- ``get_manifest_schema``: a resource kind's example manifest, and its fields.
- ``validate_manifest``: a manifest's errors, from the platform's own validation.
See :mod:`smarter.apps.mcpserver.schemas.schemas`.
.. note::
**Experimental.** The MCPServer was designed and coded by Claude Code (Anthropic's
Claude Opus 5.5), with Lawrence McDaniel as co-author. It is experimental, and will
be documented.
"""
from typing import Optional, cast
import yaml
from pydantic import Field
from smarter.apps.api.v1.contract.const import SAM_SCHEMA_SUFFIX
from smarter.apps.mcpserver.exceptions import SmarterMCPServerToolError
from smarter.apps.mcpserver.manifests import schemas
from smarter.apps.mcpserver.protocol.tools import (
MAX_READ_CHARACTERS,
SmarterProjectTool,
ToolArguments,
)
from smarter.apps.mcpserver.repositories import ProjectRepository, get_repository
from smarter.common.const import VERSION
from smarter.lib import json
# -----------------------------------------------------------------------------
# get_manifest_schema
# -----------------------------------------------------------------------------
class GetManifestSchemaArguments(ToolArguments):
"""The arguments of get_manifest_schema."""
kind: Optional[str] = Field(
default=None,
max_length=64,
description="The resource kind, e.g. LLMClient, Guardrail or MCPClient. Omit it to list the kinds.",
)
[docs]
class GetManifestSchemaTool(SmarterProjectTool):
"""A resource kind's example manifest, and the fields of its JSON Schema."""
name = "get_manifest_schema"
title = "Get manifest schema"
description = (
"Return a Smarter resource kind's example manifest, in YAML, and every field that its manifest may "
"have: its path, type, whether it is required, its default and its description, from the JSON "
"Schema that this platform validates manifests with. Use it before writing, fixing or explaining a "
"manifest, rather than guessing its fields. Omit kind to list the kinds."
)
arguments_model = GetManifestSchemaArguments
uses_repository = False
[docs]
def run(self, arguments: ToolArguments) -> str:
arguments = cast(GetManifestSchemaArguments, arguments)
kinds = schemas.manifest_kinds()
if not arguments.kind:
return (
"# Smarter resource kinds\n\nEach kind's manifest is a YAML file with apiVersion, kind, metadata "
"and spec. Call get_manifest_schema with one of these kinds for its example and fields:\n\n"
+ "\n".join(f"- {kind}" for kind in kinds)
)
kind = schemas.find_kind(arguments.kind)
if kind is None:
raise SmarterMCPServerToolError(
f"{arguments.kind} is not a resource kind. It is one of: {', '.join(kinds)}."
)
schema = schemas.manifest_schema(kind)
example = yaml.safe_dump(schemas.manifest_example(kind), sort_keys=False, allow_unicode=True).rstrip()
schema_url = self.schema_url(kind)
header = (
f"# {kind} manifest\n\n{schema.get('description', '')} The example and fields are this platform's, "
f"version {VERSION.get('__version__', '0.0.0')}. Apply a manifest with `smarter apply -f <file>.yaml`.\n"
f"Full JSON Schema: {schema_url}\n\n## Example\n\n```yaml\n{example}\n```\n\n## Fields\n\n"
"The read-only status, which the platform writes, is left out. `[]` is an item of a list, and "
"`<key>` a key of a map.\n"
)
lines, characters, fields = [], len(header), schemas.manifest_fields(schema)
for number, field in enumerate(fields):
line = self.format_field(field)
if characters + len(line) + 1 > MAX_READ_CHARACTERS:
lines.append(f"\n[{len(fields) - number} more fields: see the full JSON Schema, {schema_url}]")
break
lines.append(line)
characters += len(line) + 1
return header + "\n".join(lines)
[docs]
@staticmethod
def schema_url(kind: str) -> str:
"""Return the GitHub url of a kind's JSON Schema, in the smarter repository."""
repository = get_repository("smarter") or ProjectRepository(
name="smarter", title="the Smarter platform", description="The Smarter platform."
)
return repository.file_url(None, f"{schemas.SAM_SCHEMA_REPOSITORY_PATH}/{kind}{SAM_SCHEMA_SUFFIX}")
[docs]
@staticmethod
def format_field(field: schemas.ManifestField) -> str:
"""Return one field, as a Markdown list item."""
details = [field.type, "required" if field.required else "optional"]
if field.default is not None:
details.append(f"default {json.dumps(field.default)}")
text = f"- `{field.path}` ({', '.join(details)})"
return f"{text}: {field.description}" if field.description else text
# -----------------------------------------------------------------------------
# validate_manifest
# -----------------------------------------------------------------------------
class ValidateManifestArguments(ToolArguments):
"""The arguments of validate_manifest."""
manifest: str = Field(
min_length=1,
max_length=schemas.MAX_MANIFEST_LENGTH,
description="The manifest, in YAML or JSON, with apiVersion, kind, metadata and spec.",
)
[docs]
class ValidateManifestTool(SmarterProjectTool):
"""Validate a manifest with the platform's own validation, without applying it."""
name = "validate_manifest"
title = "Validate manifest"
description = (
"Validate a Smarter manifest, in YAML or JSON, against the model of its kind: the validation "
"that `smarter apply` does first, without applying it. Returns whether it is valid, and each "
"error with the path of its field. Call it on every manifest that you write or fix, before you "
"give it to the user, and fix what it reports. Checks that need the network, e.g. that an image "
"url responds, are skipped."
)
arguments_model = ValidateManifestArguments
uses_repository = False
[docs]
def run(self, arguments: ToolArguments) -> str:
arguments = cast(ValidateManifestArguments, arguments)
validation = schemas.validate_manifest_text(arguments.manifest)
if validation.valid:
return (
"The manifest is valid. Apply it with `smarter apply -f <file>.yaml`. The platform may still "
"refuse it for something outside the manifest, e.g. a Secret that it names does not exist."
)
errors = "\n".join(
f"- `{'.'.join(str(part) for part in error.loc) or '(manifest)'}`: {error.message}"
for error in validation.errors
)
return (
f"The manifest has {len(validation.errors)} errors:\n\n{errors}\n\n"
"Fix them, and validate it again. get_manifest_schema lists every field of a kind."
)
__all__ = ["GetManifestSchemaTool", "ValidateManifestTool"]