Skip to content

fix: Typescript schema types - move object types to Record<string, unknown>#2139

Open
KKonstantinov wants to merge 9 commits intomodelcontextprotocol:mainfrom
KKonstantinov:feature/spec-types-object-type
Open

fix: Typescript schema types - move object types to Record<string, unknown>#2139
KKonstantinov wants to merge 9 commits intomodelcontextprotocol:mainfrom
KKonstantinov:feature/spec-types-object-type

Conversation

@KKonstantinov
Copy link

@KKonstantinov KKonstantinov commented Jan 23, 2026

Replace object with Record<string, unknown> in schema/draft/schema.ts to accurately model JSON objects and avoid allowing non‑JSON values (functions, class instances, etc.).

This tightens typing relied upon by the TypeScript SDK, which currently uses a workaround way to map its types to the object specification here.

Motivation and Context

  • object in TypeScript means “any non‑primitive,” which includes arrays, functions, Dates, Maps, and class instances. That’s broader than a JSON object and can mask invalid payloads for a wire protocol. Further, some types which fall under object might not be serializable at all (thus would break serialization and the request being sent in the first place).
  • The spec’s TypeScript types are consumed directly by the TypeScript SDK and by server/client implementers. Using object is too loose in JS/TS and weakens static guarantees; Record<string, unknown> better communicates intent and prevents accidental misuse.
  • Aligns with existing uses like MetaObject = Record<string, unknown> and common JSON typing practice, improving consistency across the schema.
  • Helps downstream tooling (schema generation, docs, validation) by constraining maps to proper JSON object shapes.

How Has This Been Tested?

  • Type-only change; no runtime behavior expected to change.
  • To be validated by running npm run generate:schema and npm run check to ensure JSON schema output and docs remain consistent.

Breaking Changes

  • No changes of intent. Developers should not be passing Date, Map, or other objects here in reality.

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

  • The TypeScript SDK relies on these spec types; tightening them prevents downstream misuse and improves safety without changing the protocol’s wire representation.
  • Affected areas include capability maps (e.g., ClientCapabilities.experimental, ServerCapabilities.experimental), JSON Schema property maps (e.g., Tool.inputSchema.properties, outputSchema.properties), metadata maps, and similar dictionary-shaped fields.

@KKonstantinov KKonstantinov requested a review from a team as a code owner January 23, 2026 08:34
Copy link
Contributor

@felixweinberger felixweinberger left a comment

Choose a reason for hiding this comment

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

This seems sensible to me if we're currently implicitly allowing things like functions to be assigned to objects that should be representing JSONSchemas.

To be clear though, doesn't Record<string, unknown> still allow e.g.:

{
   "someKey": () => "hello world",
}

i.e. still not valid JSON? Should we potentially tighten it even more (is it even possible?)

E.g. would it be worth going further, e.g. something like this?

type JSONValue = string | number | boolean | null | JSONObject | JSONArray;
type JSONObject = { [key: string]: JSONValue };
type JSONArray = JSONValue[];

@KKonstantinov
Copy link
Author

KKonstantinov commented Jan 23, 2026

This seems sensible to me if we're currently implicitly allowing things like functions to be assigned to objects that should be representing JSONSchemas.

To be clear though, doesn't Record<string, unknown> still allow e.g.:

{
   "someKey": () => "hello world",
}

i.e. still not valid JSON? Should we potentially tighten it even more (is it even possible?)

E.g. would it be worth going further, e.g. something like this?

type JSONValue = string | number | boolean | null | JSONObject | JSONArray;
type JSONObject = { [key: string]: JSONValue };
type JSONArray = JSONValue[];

Yes, it does - it only resolves the top level requirements.

I agree, that going further and limiting to JSON types would be even better!

EDIT: Done!

@felixweinberger
Copy link
Contributor

@localden not super familiar with the SEP pipeline, bubbling this one up in case we need anything further to potentially get this in? E.g. do we need a core maintainer sponsor for this kind of change.

@localden localden added the schema Work related to schema changes. label Feb 3, 2026
Comment on lines +1 to +12
/**
* JSON types
*/
export type JSONValue =
| string
| number
| boolean
| null
| JSONObject
| JSONArray;
export type JSONObject = { [key: string]: JSONValue };
export type JSONArray = JSONValue[];
Copy link
Member

Choose a reason for hiding this comment

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

I think it would also be good for these to show up in the Schema Reference. We can add @category tags to enable that:

Suggested change
/**
* JSON types
*/
export type JSONValue =
| string
| number
| boolean
| null
| JSONObject
| JSONArray;
export type JSONObject = { [key: string]: JSONValue };
export type JSONArray = JSONValue[];
/* JSON types */
/**
* @category Common Types
*/
export type JSONValue =
| string
| number
| boolean
| null
| JSONObject
| JSONArray;
/**
* @category Common Types
*/
export type JSONObject = { [key: string]: JSONValue };
/**
* @category Common Types
*/
export type JSONArray = JSONValue[];

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

Labels

schema Work related to schema changes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants