Skip to content

feat(rpc)!: support Standard Schema for RPC definitions with runtime validation - #157

Merged
antfu merged 9 commits into
mainfrom
feat/rpc-standard-schema
Aug 3, 2026
Merged

feat(rpc)!: support Standard Schema for RPC definitions with runtime validation#157
antfu merged 9 commits into
mainfrom
feat/rpc-standard-schema

Conversation

@antfubot

@antfubot antfubot commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

What

RPC function definitions accept any Standard Schema validator for args/returns — valibot, zod, arktype, or devframe's built-in builder — and declared schemas are enforced at runtime. Devframe's core no longer depends on any specific validator.

Why

Schemas used to be valibot-only and inert (type inference + JSON-schema only). Standing on the Standard Schema interface lets tools bring whichever validator they already use, makes declared schemas a real boundary guarantee, and removes valibot as a forced dependency of devframe.

How

RPC layer (validator-neutral + enforced)

  • RpcArgsSchema / RpcReturnSchema and arg/return inference key off StandardSchemaV1 (new zero-runtime @standard-schema/spec dep).
  • getRpcHandler wraps the resolved handler so local, over-the-wire, and agent/MCP calls all validate declared args/returns via schema['~standard'].validate(). Failures raise DF0043 (arg) / DF0044 (return). Validation is guard-only — payloads are never rewritten, so extra object fields survive.

Fully validator-neutral core (no forced valibot)

  • New devframe/utils/schema: a tiny zero-dependency Standard Schema builder (s) — string/number/boolean/void/null/picklist/array/object/optional/nullable.
  • Recipes (open-in-editor/finder, interactive-auth) converted off valibot to the builder.
  • CLI-flags parser validates via the ~standard contract and duck-types the schema kind (works for the builder and valibot; other vendors degrade to value-flags).
  • @valibot/to-json-schema dropped; the agent/MCP surface advertises a permissive object schema for any declared args/returns.
  • valibot is now only a devDependency (tests). Verified: no valibot in devframe runtime deps, no valibot import in shipped dist.

BREAKING CHANGE

  • Declared args/returns schemas are now enforced at runtime; a payload that doesn't match its schema throws DF0043/DF0044 where it previously ran. Guard-only validation still allows extra object fields, but a schema stricter than reality will reject calls that used to pass.
  • The exported recipe definitions type their args/returns as the built-in DevframeSchema instead of valibot schema types, and @valibot/to-json-schema is no longer a devframe dependency. Agent tool JSON schemas are now permissive objects rather than precise valibot conversions.

Docs: docs/guide/rpc.md (validator-neutral + runtime-enforcement warning + built-in s), docs/guide/standalone-cli.md, docs/helpers/common-rpc-functions.md, and new docs/errors/DF0043.md / DF0044.md.

This PR was created with the help of an agent.

…te at runtime

Widen `args`/`returns` from valibot's `GenericSchema` to the
`StandardSchemaV1` interface so valibot, zod, arktype, and any other
Standard Schema validator work interchangeably, and infer handler
types from the schema's Standard Schema input types.

Every invocation path (local, over-the-wire, agent/MCP) now validates
declared `args`/`returns` at the boundary via `getRpcHandler`, throwing
coded diagnostics DF0043 / DF0044 on failure. Validation is guard-only:
payloads are never rewritten, so schemas describing a subset of an
object don't strip the sender's extra fields.

The MCP JSON-schema surface keeps valibot's converter for precise
schemas and degrades non-valibot vendors to a permissive object schema.
@netlify

netlify Bot commented Aug 1, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit ed7cba0
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a6ff30d58d3e400086588a5
😎 Deploy Preview https://deploy-preview-157--devfra.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@antfubot antfubot changed the title feat(rpc): support Standard Schema for RPC definitions with runtime validation feat(rpc)!: support Standard Schema for RPC definitions with runtime validation Aug 1, 2026
antfubot and others added 6 commits August 2, 2026 23:48
Reconcile MCP SDK v2 migration (#156) with Standard Schema support:
keep both the new @modelcontextprotocol/server Tool import and the
@standard-schema/spec import in build-server, and add @standard-schema/spec
alongside the v2 MCP catalog entries.
…t dep)

Remove valibot and @valibot/to-json-schema from devframe's runtime
dependencies so nothing forces a specific Standard Schema validator on
users. Consumers bring their own (valibot, zod, arktype) or use the new
built-in zero-dependency builder.

- Add devframe/utils/schema: a tiny zero-dep Standard Schema builder (s)
  exposing string/number/boolean/void/null/picklist/array/object/
  optional/nullable, used by the recipes.
- Convert recipes (common-rpc-functions, interactive-auth) off valibot
  to the built-in builder.
- Make the CLI-flags parser validator-neutral: validate via the
  ~standard contract and duck-type the schema kind (works for the
  builder and valibot; other vendors degrade to value-flags).
- Drop @valibot/to-json-schema; the agent/MCP surface advertises a
  permissive object schema for any declared args/returns.
- valibot stays a devDependency for tests only.

BREAKING CHANGE: the exported recipe definitions (openInEditor,
openInFinder, interactive-auth functions) now type their args/returns as
the built-in DevframeSchema instead of valibot schema types, and
@valibot/to-json-schema is no longer a devframe dependency. Agent tool
JSON schemas are now permissive objects rather than precise valibot
conversions.
…ins; docs use valibot

- Rename devframe/utils/schema -> devframe/utils/simple-schema and rename
  the exported type DevframeSchema -> SimpleSchema. The builder is now
  explicitly documented as discouraged for app code (a minimal, best-effort
  validator for devframe's own first-party packages).
- Extend the builder with record/union/literal and make object() infer
  optional keys for optional() fields (matching valibot/zod).
- Migrate the built-in plugins (assets, og, terminals) off valibot onto
  devframe/utils/simple-schema, and drop valibot from their dependencies.
- Docs: never reference the built-in builder; all schema examples use
  valibot with an explicit install hint (npm i valibot).

BREAKING CHANGE: devframe/utils/schema is renamed to
devframe/utils/simple-schema and its exported type DevframeSchema is
renamed to SimpleSchema.
…d-json

Adopt @standard-community/standard-json (dispatches on the schema's
~standard vendor, per-vendor converters are optional peers) so the
agent/MCP surface and the inspect plugin produce precise JSON schema for
whatever validator a user brings — valibot, zod, arktype — while forcing
none. Missing/unknown converters degrade to a permissive object schema.

- Core MCP to-json-schema + build-server tool projection are now async
  (lazy vendor-converter import).
- inspect's schema converter switches from @valibot/to-json-schema to
  standard-json (async), gaining any-vendor support.
- Docs: recommend valibot as the lightest validator, but note that
  json-render and the MCP server already use zod, so users pulling zod via
  those integrations should prefer zod to reuse the dependency.
- AGENTS.md: first-party packages (devframe, @devframes/*) must stay
  validator-neutral (no preferred validator dependency); docs guide users
  to valibot or zod for their own integrations.
…-json + quansync

Replace @standard-community/standard-json with the schema's own Standard
JSON Schema converter (~standard.jsonSchema from @standard-schema/spec,
implemented by e.g. zod 4). This keeps JSON-schema generation
vendor-neutral and precise for validators that ship a converter, while
removing the @standard-community/standard-json + quansync dependencies and
reverting the async conversion back to synchronous.

Validators without a native converter degrade to a permissive object
schema. The inspect plugin additionally keeps @valibot/to-json-schema as a
fallback so it still converts valibot schemas precisely.

Technique adapted from PR #155.
The snapshot was previously updated from an incremental dist that
rendered multi-entry types via subpath specifiers (devframe/rpc,
devframe/types); a clean build (as CI does) canonicalizes them to the
bare devframe entry. Regenerated to match CI.
@antfu
antfu merged commit ead15e9 into main Aug 3, 2026
12 checks passed
antfubot added a commit that referenced this pull request Aug 3, 2026
main landed two breaking changes this branch didn't know about:
- deps!: migrate MCP adapter to @modelcontextprotocol/sdk v2 (#156) —
  the monolithic package split into @modelcontextprotocol/server +
  @modelcontextprotocol/client; setRequestHandler moved from imported
  schema constants to method-string form.
- feat(rpc)!: support Standard Schema for RPC definitions (#157) —
  RpcArgsSchema/RpcReturnSchema key off StandardSchemaV1 instead of
  valibot's GenericSchema; @valibot/to-json-schema dropped; single-arg
  JSON-Schema unwrapping removed (always arg0/arg1 now).

Adaptations:
- connect.ts: import from @modelcontextprotocol/server(+/stdio) and
  @modelcontextprotocol/client (dynamic, still peer-optional); handler
  registration uses 'tools/list'/'tools/call' method strings.
- devframe's package.json: @modelcontextprotocol/client added as an
  optional peer (connect.ts uses it to dial discovered instances) and
  bundled correctly via tsdown's onlyBundle (client pulls in
  @modelcontextprotocol/core, pkce-challenge, eventsource[-parser],
  jose — all now declared).
- Renumbered DF0042 (registry write failure) and DF0043 (missing MCP
  SDK) to DF0045/DF0046 — both collided with codes main allocated to
  unrelated diagnostics (capabilities.build:false; RPC arg/return
  validation) while this branch was in flight.
- AgentTool/AgentToolInput.args and DevframeCommandAgentOptions.args
  retyped from valibot's GenericSchema[] to StandardSchemaV1[].
  host-agent.ts no longer eagerly converts args to inputSchema (that
  module is gone); a kind: 'tool' entry now carries args raw, mirroring
  how an RPC-backed tool defers to ctx.rpc.definitions — the MCP
  adapter's computeInputSchema converts either on demand.
- hub's commands→agent bridge: coercePositionalArgs no longer detects
  a single-object schema to unwrap (that convention is gone project-
  wide); always maps arg0/arg1/... positionally, matching RPC-backed
  tool coercion.
- Tests updated for the new args-carried-raw contract, plus a new
  end-to-end MCP-adapter test proving Standard Schema args convert to
  JSON Schema over the real wire (arg0-keyed, not unwrapped).
- Removed the now-fully-redundant devframe/utils/valibot-json-schema
  module and its registrations (superseded by the upstream
  to-json-schema.ts, which already covers every validator via
  ~standard.jsonSchema, degrading to a permissive object schema for
  validators without one — e.g. valibot).

Verified: 1020 unit tests, typecheck (21/21), lint, full build, and
17/17 Playwright e2e (incl. both connector round-trips through the
real stdio/HTTP MCP v2 pipeline) all green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants