Skip to content

v2 backwards compat: specTypeSchema exported - synchronous StandardSchemaV1#2047

Open
KKonstantinov wants to merge 3 commits into
mainfrom
feature/fix-sync-validate
Open

v2 backwards compat: specTypeSchema exported - synchronous StandardSchemaV1#2047
KKonstantinov wants to merge 3 commits into
mainfrom
feature/fix-sync-validate

Conversation

@KKonstantinov
Copy link
Copy Markdown
Contributor

Add StandardSchemaV1Sync type so specTypeSchemas entries expose synchronous validate

Motivation and Context

specTypeSchemas exposes every MCP spec schema as a StandardSchemaV1. The StandardSchemaV1 interface defines validate as returning Result<Output> | Promise<Result<Output>>, because the Standard Schema spec supports async validation libraries. However, every entry in specTypeSchemas is backed by Zod, which always validates synchronously -- the Promise variant is never returned at runtime.

This causes a real problem for consumers: calling specTypeSchemas.X['~standard'].validate(v) and then accessing .issues or .value on the result produces a type error, because TypeScript thinks the result might be a Promise.

const result = specTypeSchemas.CreateMessageResult['~standard'].validate(data);
// TS error: Property 'issues' does not exist on type 'Result<...> | Promise<Result<...>>'
if (result.issues !== undefined) { ... }

How Has This Been Tested?

  • pnpm typecheck:all -- all packages typecheck cleanly
  • pnpm test:all -- all 1532 tests pass across all packages

Breaking Changes

No. StandardSchemaV1Sync extends StandardSchemaV1, so specTypeSchemas entries remain assignable anywhere a StandardSchemaV1 is expected. The change only narrows the return type of validate from Result | Promise<Result> to Result.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Three files changed (types only, no runtime changes):

  • packages/core/src/util/standardSchema.ts -- defines StandardSchemaV1Sync, which extends StandardSchemaV1 but narrows validate to return StandardSchemaV1.Result<Output> (no Promise variant)
  • packages/core/src/types/specTypeSchema.ts -- SchemaRecord now uses StandardSchemaV1Sync instead of StandardSchemaV1
  • packages/core/src/exports/public/index.ts -- exports StandardSchemaV1Sync so consumers can reference the type

APIs that accept user-provided schemas (e.g. McpServer.tool()) still use StandardSchemaV1 / StandardSchemaWithJSON, so async validation libraries remain fully supported.

@KKonstantinov KKonstantinov requested a review from a team as a code owner May 11, 2026 15:22
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 11, 2026

⚠️ No Changeset found

Latest commit: 348d3a2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes changesets to release 6 packages
Name Type
@modelcontextprotocol/client Minor
@modelcontextprotocol/server Minor
@modelcontextprotocol/express Major
@modelcontextprotocol/fastify Major
@modelcontextprotocol/hono Major
@modelcontextprotocol/node Major

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 11, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2047

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2047

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2047

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2047

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2047

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2047

commit: 348d3a2

Comment thread packages/core/src/types/specTypeSchema.ts
Comment thread packages/core/src/exports/public/index.ts
…wing

  Replace intersection-based StandardSchemaV1Sync with interface-extends
  approach so TypeScript properly narrows validate() return type.
  Drop async/await from examples and migration docs to match the
  synchronous API this PR introduces.
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the earlier feedback — LGTM. The two remaining inline notes are non-blocking polish (vestigial test casts and the InferInput/InferOutput namespace aliases); fine to take or leave.

Extended reasoning...

Overview

This PR adds a StandardSchemaV1Sync interface (a strict subtype of StandardSchemaV1 whose validate is guaranteed to return synchronously) and retypes specTypeSchemas to use it. After my first-round review on commit 34bccc9, commit 5bdaf6c updated the changeset prose, the .examples.ts snippet, the synced JSDoc @example, and both migration docs to drop await and reference StandardSchemaV1Sync — both prior nits are fully addressed.

Security risks

None. This is a type-level narrowing with zero runtime change: _specTypeSchemas is still populated with the same Zod schemas and frozen the same way. No auth, transport, parsing, or wire behavior is touched.

Level of scrutiny

Low–medium. The only question is soundness of the narrowing, and it holds: StandardSchemaV1Sync extends StandardSchemaV1 and only narrows the return type of validate (covariant position), so every specTypeSchemas entry remains assignable wherever StandardSchemaV1 is expected. The runtime claim is also valid — every entry is a plain Zod spec schema with no async refinements, so ~standard.validate always returns synchronously and the as SchemaRecord cast is sound. The new public type export has a concrete in-tree consumer (SchemaRecord) and is now documented in the changeset and migration guides, satisfying the repo's "every new export is intentional" bar.

Other factors

The two new inline nits (leftover as { issues?: ... } casts in specTypeSchema.test.ts, and the missing InferInput/InferOutput aliases in the StandardSchemaV1Sync namespace) are consistency/cleanup items that don't affect correctness, the published API shape, or backwards compatibility. They can be folded in here or taken as a follow-up.

Comment thread packages/core/src/types/specTypeSchema.ts
Comment thread packages/core/src/util/standardSchema.ts
…rInput/InferOutput aliases

  - Remove unnecessary `as { issues?: ... }` casts in specTypeSchema.test.ts
    that were workarounds for the old async validate return type. The bare
    `result.issues` access now serves as compile-time regression coverage
    for the StandardSchemaV1Sync narrowing.
  - Add InferInput/InferOutput convenience aliases to the StandardSchemaV1Sync
    namespace, matching the pattern of all sibling Standard* namespaces.
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.

1 participant