Skip to content

matthewjberger/enum2schema

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

enum2schema

github crates.io docs.rs

enum2schema derives a JSON Schema from a Rust type, so the schema a consumer reads (for example an MCP tool inputSchema) lives next to the type instead of in a separate hand written serde_json::json! literal.

It carries /// doc comments into description, models enums with serde's default externally tagged representation, and maps Rust types to their JSON Schema equivalents. Leaf types that should not derive Schema, such as math types or fixed color arrays, are described with field attributes.

Why enum2schema

enum2schema is small and focused: a derive that emits an inline JSON Schema matching serde's output, for when a schema exists so a person or an agent can read it (for example an MCP tool inputSchema).

  • Tiny dependency. Just serde_json and a derive, with no schema runtime to pull into a game engine or a wasm worker that only needs to describe a handful of types.
  • Inline, not $ref. Every schema is self-contained, which is easier for an LLM (or a human) to read than a web of $refs into a definitions table. Recursive types drop the recursive edge with #[schema(skip)].
  • Built for non-deriving leaf types. Math vectors, quaternions, matrices, and fixed color arrays are described in place with #[schema(type = "array", items = "number", len = 3)] or #[schema(with = ...)], with no newtype wrappers or upstream impls for types like nalgebra's.
  • Matched to serde. It models serde's default externally tagged enums (plus a string_enum mode) and respects rename, rename_all, and default, so the schema reflects the real wire format.

It targets serde's externally tagged enums and inline schemas, which is what most tool and message contracts use.

Usage

Add this to your Cargo.toml:

enum2schema = "0.1"

Example:

use enum2schema::Schema;

/// A command for the worker.
#[derive(Schema)]
enum Command {
    /// Stop everything.
    Stop,
    /// Move to a point.
    Move { x: f32, y: f32 },
}

let schema = Command::schema();

schema() returns a serde_json::Value holding a JSON Schema fragment. The consumer decides how to wrap it: a host wraps an args struct's schema as an MCP tool inputSchema; a worker uses a type's schema directly.

Types

Scalars map to number, integer, boolean, and string. Option<T> is left out of required and made nullable. Vec<T>, slices, and sets become array; fixed length arrays [T; N] and tuples become an array carrying minItems/maxItems. HashMap/BTreeMap with string keys become an object with additionalProperties. Box, Rc, and Arc are transparent. serde_json::Value becomes the any schema. Nested types that derive Schema recurse. Generic types are supported: each type parameter is bounded by Schema, so Envelope<T> derives a schema once T: Schema.

Enums use serde's externally tagged form: a unit variant is a string const inside a oneOf, and a data variant is a single key object. For a closed set of unit variants, #[schema(string_enum)] emits a single string type with an enum list instead.

MCP tools

The mcp module builds an MCP tool descriptor straight from the argument type, so the inputSchema lives next to the args struct instead of a hand written literal:

use enum2schema::Schema;

/// Move an entity.
#[derive(Schema)]
struct MoveArgs {
    entity: u32,
    to: [f32; 3],
}

let descriptor = enum2schema::mcp::tool::<MoveArgs>("move", "Move an entity");
// { "name": "move", "description": "Move an entity", "inputSchema": <MoveArgs schema> }

tool_io::<Args, Reply>(name, description) additionally emits an outputSchema from the result type, for MCP structured tool results. The actual request and response values are still built by serde (serde_json::to_value); enum2schema only supplies the schemas.

Attributes

Field attributes:

  • #[schema(with = path::to::fn)] uses path::to::fn() (returning serde_json::Value) as the field schema.
  • #[schema(type = "array", items = "number", len = 3)] describes a fixed shape for a type that does not implement Schema.
  • #[schema(description = "...")] sets or overrides the description.
  • #[schema(skip)] omits the field, or, on an enum variant, omits that variant from the schema (serde still serializes it). Use it to drop a recursive variant so an otherwise cyclic type can derive Schema.

Container attribute:

  • #[schema(string_enum)] on a unit only enum emits a string type with an enum list.

The serde attributes #[serde(rename = "...")], #[serde(rename_all = "...")], and #[serde(default)] (treated as not required) are respected.

A single use enum2schema::Schema; brings in both the trait and the derive; the crate also re-exports serde_json.

About

Derive a JSON Schema from a Rust type (doc comments, externally tagged enums, generics, MCP tool descriptors)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages