Skip to content

feat: generate JSON Schema for tool inputs from proto descriptors (port from Go) #5

Closed

Description

@erikist

Part of #2. Port Go's cmd/protoc-gen-go-mcp/schema.go, with schema_test.go and testdata/schemapb/schema.proto, at 9e072f9. The output is the JSON Schema (draft 2020-12) for an RPC's request message, used as the tool's inputSchema. It must describe exactly what the pbjson-generated serde accepts. Go's equivalent describes what protojson.Unmarshal accepts, and the two mappings agree on everything below. Where pbjson differs, call it out in the PR.

Rules (same as Go; read the doc comments in schema.go for the reasoning)

  • Messages: {"type":"object","properties":{…},"additionalProperties":false}, with description taken from the message's leading comment.
    • No required (proto3 has no required fields). No field accepts null.
    • Property names are the field's json_name (lowerCamel), never the snake_case proto name.
  • Fields: description comes from the leading comment. For a member of a real (non-synthetic) oneof, append Part of the "<oneof>" oneof: at most one of its fields may be set. A proto3 optional field is just a normal property.
  • Scalars:
    • bool → boolean; string → string; bytes → {"type":"string","contentEncoding":"base64"}.
    • int32/sint32/sfixed32 → integer; uint32/fixed32 → integer with minimum: 0.
    • int64/sint64/sfixed64 → {"type":["integer","string"]}; uint64/fixed64 → the same plus minimum: 0.
    • float/double → anyOf a number, or a string from the enum NaN, Infinity, -Infinity.
  • Enums: {"type":"string","enum":[value names…]}, with the enum's description. Names only, no numbers.
  • Repeated and maps: repeated → {"type":"array","items":…}. map → {"type":"object","additionalProperties":<value schema>}, where the value schema comes from the map entry's field number 2.
  • Well-known types:
    • Timestamp → string with format: date-time; Duration → string; FieldMask → string.
    • Struct → object; Value → {}; ListValue → array; Empty → object with additionalProperties: false.
    • Any → object with required @type string and additional properties allowed.
    • Wrapper types → the schema of the scalar they wrap.
  • Recursion: messages in a reference cycle become $defs entries keyed by full name, referenced as {"$ref":"#/$defs/<full.name>"}. Everything else is inlined. If the root message is itself recursive, unwrap it so the root is still "type":"object" (see MessageInputSchema).
  • Build the schema as a serde_json::Value with sorted keys, i.e. without serde_json's preserve_order feature, so the output is deterministic and orders keys like Go's.

Acceptance criteria

  • Copy schema.proto into this repo's test data, and port every test in schema_test.go, including the validation tests (TestSchemaValidates*, TestAnySchemaAllowsAdditionalProperties, TestSchemaValidatesNonFiniteFloats, TestMessageInputSchema_RecursiveRootMessage). Validate with the jsonschema crate against pbjson-serialized messages, the counterpart of Go's protojson-marshaled ones.
    • Get descriptors with source info from protox in tests (a pure-Rust protobuf compiler, like Go's protocompile) or from protoc. Either way, cargo test must not depend on network access.
  • Parity: for each request message in example.proto, the schema is JSON-equal (compare parsed values, not bytes, because Go's encoder HTML-escapes <>&) to the InputSchema in Go's committed examples/gen/example/v1/example_mcp.pb.go.
  • Any pbjson/protojson difference you find (e.g. an Any encoding) is listed in the PR, with a test that pins down the current behavior.
  • The checks in AGENTS.md pass.

Activity

  1. erikist commented on Oct 9, 2026

    @erikist
    ContributorAuthor

    🤖 Factory: picked up by dispatcher; working on branch factory/5-feat-generate-json-schema-for-tool.

  2. erikist commented on Oct 9, 2026

    @erikist
    ContributorAuthor

    🤖 Factory: opened PR #18 implementing this issue: #18

    Summary: ports Go's schema.go to a new protoc_gen_rust_mcp::schema module, with the vendored schemapb/schema.proto test data, all of schema_test.go's cases (including the property/validation tests), and a parity test against Go's committed example_mcp.pb.go input schemas. Also documents and tests two pbjson/protojson differences found along the way: google.protobuf.Any and google.protobuf.FieldMask both get ordinary (not protojson-special-cased) message serde from pbjson, unlike Timestamp/Duration/Struct/Value/ListValue/the wrappers.

    All of AGENTS.md's checks pass (cargo fmt, cargo clippy -D warnings, cargo build --workspace, cargo test --workspace), and make generate still produces no diff in examples/src/gen (this module isn't wired into the generator yet — that's issue #6). See the PR body for full details and test evidence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    factory:in-progressAn sf-factory worker is on itfactory:readyReady for the sf-factory to pick up

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions