registerTool: accept ZodType<object> for input and output schema - #816

Merged
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main
Nov 7, 2025
Merged

registerTool: accept ZodType<object> for input and output schema#816
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main

Conversation

@ksinder

@ksinderksinder commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Motivation and Context

See #588. This PR adds a backwards-compatible change to registerTool on McpServer to allow any Zod schema extending ZodType<object> instead of requiring inputSchema and outputSchema to conform to ZodRawShape (:= Record<string, ZodType<any>>).

The underlying schema validation and zod-to-json-schema dependency already support this, so adding this plumbing expands SDK expressiveness and flexibility. Specifically, this unblocks MCP servers' ability to define tool schemas that use z.union(...) and z.intersection(...). Historically, some creators have had to wrap existing schemas in some wrapper object that has fixed keys, e.g. {bodyParams: z.union(...)} to work around this limitation.

How Has This Been Tested?

Added automated tests in mcp.test.ts and confirmed they pass locally with npm run test.

Also manually tried changing the test locally to use inputSchemas for types that aren't objects at the top level -- e.g. z.number() and z.union([z.number(), z.object({})]), and confirmed typechecking fails (which is good, since I'm guessing we still want guardrails to push toward objects for params and responses. If this ever changes in the future it's easy enough to change all ZodType<object> to ZodTypeAny!)

image

Breaking Changes

Should be backwards-compatible in the SDK with existing server code. This is purely additive since we can easily dynamically distinguish between existing ZodRawShapes being passed in vs. direct ZodType<object> and branch accordingly. ZodType<object> is added as a union type variant for server.registerTool, and isn't mandatory.

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

N/A

@jjn1056

Copy link
Copy Markdown

+1 on merging this or at least getting feedback from the team. I ran into this problem myself just today. Some of the error conditions that the API I'm wrapping for LLM consumption are complex and the better I can hint the LLM in the response, the better we'll be able to properly recover.

@akshaylingamaneni

Copy link
Copy Markdown

guys, can we get this merged

@davidgilbertson

Copy link
Copy Markdown

It's disappointing that @ksinder went to the trouble of creating a PR for this and it's been ignored for many months.

@ksinder
ksinder requested a review from a team as a code ownerNovember 5, 2025 19:10
@ksinder
ksinderforce-pushed the main branch 2 times, most recently from 47678e4 to d839335CompareNovember 5, 2025 19:21

@pcarletonpcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@pcarleton
pcarleton merged commit 806d7cf into modelcontextprotocol:mainNov 7, 2025
2 checks passed
@demian85

Copy link
Copy Markdown

Can I get help with this please? #1148

@RahulLanjewar93

RahulLanjewar93 commented Nov 27, 2025

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0
I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me
zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong
The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

@RahulLanjewar93

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0 I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

The issue was due to a difference in exported types from the sdk. I was using low-level server, and the type I used for creating the tool was Tool, where it should have been RegisteredTool don't know why there are two of them. Should be documented somewhere, or need to create a bug if these two are supposed to be used interchangeably

// GOOD import{RegisteredTool,RegisteredResource}from'@modelcontextprotocol/sdk/server/mcp';// BADimport{Tool,Resource}from'@modelcontextprotocol/sdk/types';

@joshjg

Copy link
Copy Markdown

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

@felixweinberger

felixweinberger commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

Hi @joshjg thanks for this - could you elaborate on what you mean that has regressed here? If you could file an issue that would be greatly appreciated!

@joshjg

Copy link
Copy Markdown

@felixweinberger Created #1308 with more details.

Sign up for freeto 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.

9 participants

@ksinder@jjn1056@akshaylingamaneni@davidgilbertson@demian85@RahulLanjewar93@joshjg@felixweinberger@pcarleton
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

registerTool: accept ZodType<object> for input and output schema - #816

Merged
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main
Nov 7, 2025
Merged

registerTool: accept ZodType<object> for input and output schema#816
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main

Conversation

@ksinder

@ksinderksinder commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Motivation and Context

See #588. This PR adds a backwards-compatible change to registerTool on McpServer to allow any Zod schema extending ZodType<object> instead of requiring inputSchema and outputSchema to conform to ZodRawShape (:= Record<string, ZodType<any>>).

The underlying schema validation and zod-to-json-schema dependency already support this, so adding this plumbing expands SDK expressiveness and flexibility. Specifically, this unblocks MCP servers' ability to define tool schemas that use z.union(...) and z.intersection(...). Historically, some creators have had to wrap existing schemas in some wrapper object that has fixed keys, e.g. {bodyParams: z.union(...)} to work around this limitation.

How Has This Been Tested?

Added automated tests in mcp.test.ts and confirmed they pass locally with npm run test.

Also manually tried changing the test locally to use inputSchemas for types that aren't objects at the top level -- e.g. z.number() and z.union([z.number(), z.object({})]), and confirmed typechecking fails (which is good, since I'm guessing we still want guardrails to push toward objects for params and responses. If this ever changes in the future it's easy enough to change all ZodType<object> to ZodTypeAny!)

image

Breaking Changes

Should be backwards-compatible in the SDK with existing server code. This is purely additive since we can easily dynamically distinguish between existing ZodRawShapes being passed in vs. direct ZodType<object> and branch accordingly. ZodType<object> is added as a union type variant for server.registerTool, and isn't mandatory.

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

N/A

@jjn1056

Copy link
Copy Markdown

+1 on merging this or at least getting feedback from the team. I ran into this problem myself just today. Some of the error conditions that the API I'm wrapping for LLM consumption are complex and the better I can hint the LLM in the response, the better we'll be able to properly recover.

@akshaylingamaneni

Copy link
Copy Markdown

guys, can we get this merged

@davidgilbertson

Copy link
Copy Markdown

It's disappointing that @ksinder went to the trouble of creating a PR for this and it's been ignored for many months.

@ksinder
ksinder requested a review from a team as a code ownerNovember 5, 2025 19:10
@ksinder
ksinderforce-pushed the main branch 2 times, most recently from 47678e4 to d839335CompareNovember 5, 2025 19:21

@pcarletonpcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@pcarleton
pcarleton merged commit 806d7cf into modelcontextprotocol:mainNov 7, 2025
2 checks passed
@demian85

Copy link
Copy Markdown

Can I get help with this please? #1148

@RahulLanjewar93

RahulLanjewar93 commented Nov 27, 2025

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0
I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me
zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong
The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

@RahulLanjewar93

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0 I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

The issue was due to a difference in exported types from the sdk. I was using low-level server, and the type I used for creating the tool was Tool, where it should have been RegisteredTool don't know why there are two of them. Should be documented somewhere, or need to create a bug if these two are supposed to be used interchangeably

// GOOD import{RegisteredTool,RegisteredResource}from'@modelcontextprotocol/sdk/server/mcp';// BADimport{Tool,Resource}from'@modelcontextprotocol/sdk/types';

@joshjg

Copy link
Copy Markdown

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

@felixweinberger

felixweinberger commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

Hi @joshjg thanks for this - could you elaborate on what you mean that has regressed here? If you could file an issue that would be greatly appreciated!

@joshjg

Copy link
Copy Markdown

@felixweinberger Created #1308 with more details.

Sign up for freeto 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.

9 participants

@ksinder@jjn1056@akshaylingamaneni@davidgilbertson@demian85@RahulLanjewar93@joshjg@felixweinberger@pcarleton
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

registerTool: accept ZodType<object> for input and output schema - #816

Merged
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main
Nov 7, 2025
Merged

registerTool: accept ZodType<object> for input and output schema#816
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main

Conversation

@ksinder

@ksinderksinder commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Motivation and Context

See #588. This PR adds a backwards-compatible change to registerTool on McpServer to allow any Zod schema extending ZodType<object> instead of requiring inputSchema and outputSchema to conform to ZodRawShape (:= Record<string, ZodType<any>>).

The underlying schema validation and zod-to-json-schema dependency already support this, so adding this plumbing expands SDK expressiveness and flexibility. Specifically, this unblocks MCP servers' ability to define tool schemas that use z.union(...) and z.intersection(...). Historically, some creators have had to wrap existing schemas in some wrapper object that has fixed keys, e.g. {bodyParams: z.union(...)} to work around this limitation.

How Has This Been Tested?

Added automated tests in mcp.test.ts and confirmed they pass locally with npm run test.

Also manually tried changing the test locally to use inputSchemas for types that aren't objects at the top level -- e.g. z.number() and z.union([z.number(), z.object({})]), and confirmed typechecking fails (which is good, since I'm guessing we still want guardrails to push toward objects for params and responses. If this ever changes in the future it's easy enough to change all ZodType<object> to ZodTypeAny!)

image

Breaking Changes

Should be backwards-compatible in the SDK with existing server code. This is purely additive since we can easily dynamically distinguish between existing ZodRawShapes being passed in vs. direct ZodType<object> and branch accordingly. ZodType<object> is added as a union type variant for server.registerTool, and isn't mandatory.

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

N/A

@jjn1056

Copy link
Copy Markdown

+1 on merging this or at least getting feedback from the team. I ran into this problem myself just today. Some of the error conditions that the API I'm wrapping for LLM consumption are complex and the better I can hint the LLM in the response, the better we'll be able to properly recover.

@akshaylingamaneni

Copy link
Copy Markdown

guys, can we get this merged

@davidgilbertson

Copy link
Copy Markdown

It's disappointing that @ksinder went to the trouble of creating a PR for this and it's been ignored for many months.

@ksinder
ksinder requested a review from a team as a code ownerNovember 5, 2025 19:10
@ksinder
ksinderforce-pushed the main branch 2 times, most recently from 47678e4 to d839335CompareNovember 5, 2025 19:21

@pcarletonpcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@pcarleton
pcarleton merged commit 806d7cf into modelcontextprotocol:mainNov 7, 2025
2 checks passed
@demian85

Copy link
Copy Markdown

Can I get help with this please? #1148

@RahulLanjewar93

RahulLanjewar93 commented Nov 27, 2025

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0
I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me
zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong
The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

@RahulLanjewar93

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0 I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

The issue was due to a difference in exported types from the sdk. I was using low-level server, and the type I used for creating the tool was Tool, where it should have been RegisteredTool don't know why there are two of them. Should be documented somewhere, or need to create a bug if these two are supposed to be used interchangeably

// GOOD import{RegisteredTool,RegisteredResource}from'@modelcontextprotocol/sdk/server/mcp';// BADimport{Tool,Resource}from'@modelcontextprotocol/sdk/types';

@joshjg

Copy link
Copy Markdown

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

@felixweinberger

felixweinberger commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

Hi @joshjg thanks for this - could you elaborate on what you mean that has regressed here? If you could file an issue that would be greatly appreciated!

@joshjg

Copy link
Copy Markdown

@felixweinberger Created #1308 with more details.

Sign up for freeto 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.

9 participants

@ksinder@jjn1056@akshaylingamaneni@davidgilbertson@demian85@RahulLanjewar93@joshjg@felixweinberger@pcarleton
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

registerTool: accept ZodType<object> for input and output schema - #816

Merged
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main
Nov 7, 2025
Merged

registerTool: accept ZodType<object> for input and output schema#816
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main

Conversation

@ksinder

@ksinderksinder commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Motivation and Context

See #588. This PR adds a backwards-compatible change to registerTool on McpServer to allow any Zod schema extending ZodType<object> instead of requiring inputSchema and outputSchema to conform to ZodRawShape (:= Record<string, ZodType<any>>).

The underlying schema validation and zod-to-json-schema dependency already support this, so adding this plumbing expands SDK expressiveness and flexibility. Specifically, this unblocks MCP servers' ability to define tool schemas that use z.union(...) and z.intersection(...). Historically, some creators have had to wrap existing schemas in some wrapper object that has fixed keys, e.g. {bodyParams: z.union(...)} to work around this limitation.

How Has This Been Tested?

Added automated tests in mcp.test.ts and confirmed they pass locally with npm run test.

Also manually tried changing the test locally to use inputSchemas for types that aren't objects at the top level -- e.g. z.number() and z.union([z.number(), z.object({})]), and confirmed typechecking fails (which is good, since I'm guessing we still want guardrails to push toward objects for params and responses. If this ever changes in the future it's easy enough to change all ZodType<object> to ZodTypeAny!)

image

Breaking Changes

Should be backwards-compatible in the SDK with existing server code. This is purely additive since we can easily dynamically distinguish between existing ZodRawShapes being passed in vs. direct ZodType<object> and branch accordingly. ZodType<object> is added as a union type variant for server.registerTool, and isn't mandatory.

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

N/A

@jjn1056

Copy link
Copy Markdown

+1 on merging this or at least getting feedback from the team. I ran into this problem myself just today. Some of the error conditions that the API I'm wrapping for LLM consumption are complex and the better I can hint the LLM in the response, the better we'll be able to properly recover.

@akshaylingamaneni

Copy link
Copy Markdown

guys, can we get this merged

@davidgilbertson

Copy link
Copy Markdown

It's disappointing that @ksinder went to the trouble of creating a PR for this and it's been ignored for many months.

@ksinder
ksinder requested a review from a team as a code ownerNovember 5, 2025 19:10
@ksinder
ksinderforce-pushed the main branch 2 times, most recently from 47678e4 to d839335CompareNovember 5, 2025 19:21

@pcarletonpcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@pcarleton
pcarleton merged commit 806d7cf into modelcontextprotocol:mainNov 7, 2025
2 checks passed
@demian85

Copy link
Copy Markdown

Can I get help with this please? #1148

@RahulLanjewar93

RahulLanjewar93 commented Nov 27, 2025

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0
I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me
zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong
The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

@RahulLanjewar93

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0 I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

The issue was due to a difference in exported types from the sdk. I was using low-level server, and the type I used for creating the tool was Tool, where it should have been RegisteredTool don't know why there are two of them. Should be documented somewhere, or need to create a bug if these two are supposed to be used interchangeably

// GOOD import{RegisteredTool,RegisteredResource}from'@modelcontextprotocol/sdk/server/mcp';// BADimport{Tool,Resource}from'@modelcontextprotocol/sdk/types';

@joshjg

Copy link
Copy Markdown

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

@felixweinberger

felixweinberger commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

Hi @joshjg thanks for this - could you elaborate on what you mean that has regressed here? If you could file an issue that would be greatly appreciated!

@joshjg

Copy link
Copy Markdown

@felixweinberger Created #1308 with more details.

Sign up for freeto 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.

9 participants

@ksinder@jjn1056@akshaylingamaneni@davidgilbertson@demian85@RahulLanjewar93@joshjg@felixweinberger@pcarleton
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

registerTool: accept ZodType<object> for input and output schema - #816

Merged
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main
Nov 7, 2025
Merged

registerTool: accept ZodType<object> for input and output schema#816
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main

Conversation

@ksinder

@ksinderksinder commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Motivation and Context

See #588. This PR adds a backwards-compatible change to registerTool on McpServer to allow any Zod schema extending ZodType<object> instead of requiring inputSchema and outputSchema to conform to ZodRawShape (:= Record<string, ZodType<any>>).

The underlying schema validation and zod-to-json-schema dependency already support this, so adding this plumbing expands SDK expressiveness and flexibility. Specifically, this unblocks MCP servers' ability to define tool schemas that use z.union(...) and z.intersection(...). Historically, some creators have had to wrap existing schemas in some wrapper object that has fixed keys, e.g. {bodyParams: z.union(...)} to work around this limitation.

How Has This Been Tested?

Added automated tests in mcp.test.ts and confirmed they pass locally with npm run test.

Also manually tried changing the test locally to use inputSchemas for types that aren't objects at the top level -- e.g. z.number() and z.union([z.number(), z.object({})]), and confirmed typechecking fails (which is good, since I'm guessing we still want guardrails to push toward objects for params and responses. If this ever changes in the future it's easy enough to change all ZodType<object> to ZodTypeAny!)

image

Breaking Changes

Should be backwards-compatible in the SDK with existing server code. This is purely additive since we can easily dynamically distinguish between existing ZodRawShapes being passed in vs. direct ZodType<object> and branch accordingly. ZodType<object> is added as a union type variant for server.registerTool, and isn't mandatory.

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

N/A

@jjn1056

Copy link
Copy Markdown

+1 on merging this or at least getting feedback from the team. I ran into this problem myself just today. Some of the error conditions that the API I'm wrapping for LLM consumption are complex and the better I can hint the LLM in the response, the better we'll be able to properly recover.

@akshaylingamaneni

Copy link
Copy Markdown

guys, can we get this merged

@davidgilbertson

Copy link
Copy Markdown

It's disappointing that @ksinder went to the trouble of creating a PR for this and it's been ignored for many months.

@ksinder
ksinder requested a review from a team as a code ownerNovember 5, 2025 19:10
@ksinder
ksinderforce-pushed the main branch 2 times, most recently from 47678e4 to d839335CompareNovember 5, 2025 19:21

@pcarletonpcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@pcarleton
pcarleton merged commit 806d7cf into modelcontextprotocol:mainNov 7, 2025
2 checks passed
@demian85

Copy link
Copy Markdown

Can I get help with this please? #1148

@RahulLanjewar93

RahulLanjewar93 commented Nov 27, 2025

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0
I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me
zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong
The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

@RahulLanjewar93

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0 I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

The issue was due to a difference in exported types from the sdk. I was using low-level server, and the type I used for creating the tool was Tool, where it should have been RegisteredTool don't know why there are two of them. Should be documented somewhere, or need to create a bug if these two are supposed to be used interchangeably

// GOOD import{RegisteredTool,RegisteredResource}from'@modelcontextprotocol/sdk/server/mcp';// BADimport{Tool,Resource}from'@modelcontextprotocol/sdk/types';

@joshjg

Copy link
Copy Markdown

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

@felixweinberger

felixweinberger commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

Hi @joshjg thanks for this - could you elaborate on what you mean that has regressed here? If you could file an issue that would be greatly appreciated!

@joshjg

Copy link
Copy Markdown

@felixweinberger Created #1308 with more details.

Sign up for freeto 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.

9 participants

@ksinder@jjn1056@akshaylingamaneni@davidgilbertson@demian85@RahulLanjewar93@joshjg@felixweinberger@pcarleton
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

registerTool: accept ZodType<object> for input and output schema - #816

Merged
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main
Nov 7, 2025
Merged

registerTool: accept ZodType<object> for input and output schema#816
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main

Conversation

@ksinder

@ksinderksinder commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Motivation and Context

See #588. This PR adds a backwards-compatible change to registerTool on McpServer to allow any Zod schema extending ZodType<object> instead of requiring inputSchema and outputSchema to conform to ZodRawShape (:= Record<string, ZodType<any>>).

The underlying schema validation and zod-to-json-schema dependency already support this, so adding this plumbing expands SDK expressiveness and flexibility. Specifically, this unblocks MCP servers' ability to define tool schemas that use z.union(...) and z.intersection(...). Historically, some creators have had to wrap existing schemas in some wrapper object that has fixed keys, e.g. {bodyParams: z.union(...)} to work around this limitation.

How Has This Been Tested?

Added automated tests in mcp.test.ts and confirmed they pass locally with npm run test.

Also manually tried changing the test locally to use inputSchemas for types that aren't objects at the top level -- e.g. z.number() and z.union([z.number(), z.object({})]), and confirmed typechecking fails (which is good, since I'm guessing we still want guardrails to push toward objects for params and responses. If this ever changes in the future it's easy enough to change all ZodType<object> to ZodTypeAny!)

image

Breaking Changes

Should be backwards-compatible in the SDK with existing server code. This is purely additive since we can easily dynamically distinguish between existing ZodRawShapes being passed in vs. direct ZodType<object> and branch accordingly. ZodType<object> is added as a union type variant for server.registerTool, and isn't mandatory.

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

N/A

@jjn1056

Copy link
Copy Markdown

+1 on merging this or at least getting feedback from the team. I ran into this problem myself just today. Some of the error conditions that the API I'm wrapping for LLM consumption are complex and the better I can hint the LLM in the response, the better we'll be able to properly recover.

@akshaylingamaneni

Copy link
Copy Markdown

guys, can we get this merged

@davidgilbertson

Copy link
Copy Markdown

It's disappointing that @ksinder went to the trouble of creating a PR for this and it's been ignored for many months.

@ksinder
ksinder requested a review from a team as a code ownerNovember 5, 2025 19:10
@ksinder
ksinderforce-pushed the main branch 2 times, most recently from 47678e4 to d839335CompareNovember 5, 2025 19:21

@pcarletonpcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@pcarleton
pcarleton merged commit 806d7cf into modelcontextprotocol:mainNov 7, 2025
2 checks passed
@demian85

Copy link
Copy Markdown

Can I get help with this please? #1148

@RahulLanjewar93

RahulLanjewar93 commented Nov 27, 2025

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0
I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me
zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong
The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

@RahulLanjewar93

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0 I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

The issue was due to a difference in exported types from the sdk. I was using low-level server, and the type I used for creating the tool was Tool, where it should have been RegisteredTool don't know why there are two of them. Should be documented somewhere, or need to create a bug if these two are supposed to be used interchangeably

// GOOD import{RegisteredTool,RegisteredResource}from'@modelcontextprotocol/sdk/server/mcp';// BADimport{Tool,Resource}from'@modelcontextprotocol/sdk/types';

@joshjg

Copy link
Copy Markdown

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

@felixweinberger

felixweinberger commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

Hi @joshjg thanks for this - could you elaborate on what you mean that has regressed here? If you could file an issue that would be greatly appreciated!

@joshjg

Copy link
Copy Markdown

@felixweinberger Created #1308 with more details.

Sign up for freeto 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.

9 participants

@ksinder@jjn1056@akshaylingamaneni@davidgilbertson@demian85@RahulLanjewar93@joshjg@felixweinberger@pcarleton
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

registerTool: accept ZodType<object> for input and output schema - #816

Merged
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main
Nov 7, 2025
Merged

registerTool: accept ZodType<object> for input and output schema#816
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main

Conversation

@ksinder

@ksinderksinder commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Motivation and Context

See #588. This PR adds a backwards-compatible change to registerTool on McpServer to allow any Zod schema extending ZodType<object> instead of requiring inputSchema and outputSchema to conform to ZodRawShape (:= Record<string, ZodType<any>>).

The underlying schema validation and zod-to-json-schema dependency already support this, so adding this plumbing expands SDK expressiveness and flexibility. Specifically, this unblocks MCP servers' ability to define tool schemas that use z.union(...) and z.intersection(...). Historically, some creators have had to wrap existing schemas in some wrapper object that has fixed keys, e.g. {bodyParams: z.union(...)} to work around this limitation.

How Has This Been Tested?

Added automated tests in mcp.test.ts and confirmed they pass locally with npm run test.

Also manually tried changing the test locally to use inputSchemas for types that aren't objects at the top level -- e.g. z.number() and z.union([z.number(), z.object({})]), and confirmed typechecking fails (which is good, since I'm guessing we still want guardrails to push toward objects for params and responses. If this ever changes in the future it's easy enough to change all ZodType<object> to ZodTypeAny!)

image

Breaking Changes

Should be backwards-compatible in the SDK with existing server code. This is purely additive since we can easily dynamically distinguish between existing ZodRawShapes being passed in vs. direct ZodType<object> and branch accordingly. ZodType<object> is added as a union type variant for server.registerTool, and isn't mandatory.

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

N/A

@jjn1056

Copy link
Copy Markdown

+1 on merging this or at least getting feedback from the team. I ran into this problem myself just today. Some of the error conditions that the API I'm wrapping for LLM consumption are complex and the better I can hint the LLM in the response, the better we'll be able to properly recover.

@akshaylingamaneni

Copy link
Copy Markdown

guys, can we get this merged

@davidgilbertson

Copy link
Copy Markdown

It's disappointing that @ksinder went to the trouble of creating a PR for this and it's been ignored for many months.

@ksinder
ksinder requested a review from a team as a code ownerNovember 5, 2025 19:10
@ksinder
ksinderforce-pushed the main branch 2 times, most recently from 47678e4 to d839335CompareNovember 5, 2025 19:21

@pcarletonpcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@pcarleton
pcarleton merged commit 806d7cf into modelcontextprotocol:mainNov 7, 2025
2 checks passed
@demian85

Copy link
Copy Markdown

Can I get help with this please? #1148

@RahulLanjewar93

RahulLanjewar93 commented Nov 27, 2025

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0
I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me
zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong
The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

@RahulLanjewar93

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0 I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

The issue was due to a difference in exported types from the sdk. I was using low-level server, and the type I used for creating the tool was Tool, where it should have been RegisteredTool don't know why there are two of them. Should be documented somewhere, or need to create a bug if these two are supposed to be used interchangeably

// GOOD import{RegisteredTool,RegisteredResource}from'@modelcontextprotocol/sdk/server/mcp';// BADimport{Tool,Resource}from'@modelcontextprotocol/sdk/types';

@joshjg

Copy link
Copy Markdown

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

@felixweinberger

felixweinberger commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

Hi @joshjg thanks for this - could you elaborate on what you mean that has regressed here? If you could file an issue that would be greatly appreciated!

@joshjg

Copy link
Copy Markdown

@felixweinberger Created #1308 with more details.

Sign up for freeto 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.

9 participants

@ksinder@jjn1056@akshaylingamaneni@davidgilbertson@demian85@RahulLanjewar93@joshjg@felixweinberger@pcarleton
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

registerTool: accept ZodType<object> for input and output schema - #816

Merged
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main
Nov 7, 2025
Merged

registerTool: accept ZodType<object> for input and output schema#816
pcarleton merged 1 commit into
modelcontextprotocol:mainfrom
ksinder:main

Conversation

@ksinder

@ksinderksinder commented Jul 28, 2025

Copy link
Copy Markdown
Contributor

Motivation and Context

See #588. This PR adds a backwards-compatible change to registerTool on McpServer to allow any Zod schema extending ZodType<object> instead of requiring inputSchema and outputSchema to conform to ZodRawShape (:= Record<string, ZodType<any>>).

The underlying schema validation and zod-to-json-schema dependency already support this, so adding this plumbing expands SDK expressiveness and flexibility. Specifically, this unblocks MCP servers' ability to define tool schemas that use z.union(...) and z.intersection(...). Historically, some creators have had to wrap existing schemas in some wrapper object that has fixed keys, e.g. {bodyParams: z.union(...)} to work around this limitation.

How Has This Been Tested?

Added automated tests in mcp.test.ts and confirmed they pass locally with npm run test.

Also manually tried changing the test locally to use inputSchemas for types that aren't objects at the top level -- e.g. z.number() and z.union([z.number(), z.object({})]), and confirmed typechecking fails (which is good, since I'm guessing we still want guardrails to push toward objects for params and responses. If this ever changes in the future it's easy enough to change all ZodType<object> to ZodTypeAny!)

image

Breaking Changes

Should be backwards-compatible in the SDK with existing server code. This is purely additive since we can easily dynamically distinguish between existing ZodRawShapes being passed in vs. direct ZodType<object> and branch accordingly. ZodType<object> is added as a union type variant for server.registerTool, and isn't mandatory.

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

N/A

@jjn1056

Copy link
Copy Markdown

+1 on merging this or at least getting feedback from the team. I ran into this problem myself just today. Some of the error conditions that the API I'm wrapping for LLM consumption are complex and the better I can hint the LLM in the response, the better we'll be able to properly recover.

@akshaylingamaneni

Copy link
Copy Markdown

guys, can we get this merged

@davidgilbertson

Copy link
Copy Markdown

It's disappointing that @ksinder went to the trouble of creating a PR for this and it's been ignored for many months.

@ksinder
ksinder requested a review from a team as a code ownerNovember 5, 2025 19:10
@ksinder
ksinderforce-pushed the main branch 2 times, most recently from 47678e4 to d839335CompareNovember 5, 2025 19:21

@pcarletonpcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@pcarleton
pcarleton merged commit 806d7cf into modelcontextprotocol:mainNov 7, 2025
2 checks passed
@demian85

Copy link
Copy Markdown

Can I get help with this please? #1148

@RahulLanjewar93

RahulLanjewar93 commented Nov 27, 2025

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0
I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me
zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong
The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

@RahulLanjewar93

Copy link
Copy Markdown

This change was released in @modelcontextprotocol/sdk@1.22.0 I am using @modelcontextprotocol/sdk@1.23.0 but still doesn't work for me zod is a direct dependency in my project zod@3.25.76

constschema=z.strictObject({key1: z.literal('value1'),key2: z.literal('value2'),});constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: schema,outputSchema: schema,};

InputSchema and outputSchema have the following error. TLDR: 'type' is missing

Property 'type' is missing in type 'ZodObject<{ key1: ZodLiteral<"value1">; key2: ZodLiteral<"value2">; }, "strict", ZodTypeAny, { key1: "value1"; key2: "value2"; }, { key1: "value1"; key2: "value2"; }>' but required in type '{ [x: string]: unknown; type: "object"; properties?: { [x: string]: object; } | undefined; required?: string[] | undefined; }'.

I think this pr is supposed to support the schema mentioned above directly. Please correct me if I am wrong The code below works fine, without any errors. But i doubt if it is the correct version

constmcpTool: Tool={name: 'Test tool',description: 'Test tool',title: 'Test tool',// just a dummy input and output schemainputSchema: { ...schema,type: 'object'},outputSchema: { ...schema,type: 'object'},};

The issue was due to a difference in exported types from the sdk. I was using low-level server, and the type I used for creating the tool was Tool, where it should have been RegisteredTool don't know why there are two of them. Should be documented somewhere, or need to create a bug if these two are supposed to be used interchangeably

// GOOD import{RegisteredTool,RegisteredResource}from'@modelcontextprotocol/sdk/server/mcp';// BADimport{Tool,Resource}from'@modelcontextprotocol/sdk/types';

@joshjg

Copy link
Copy Markdown

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

@felixweinberger

felixweinberger commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

This appears to have regressed after the changes in #1040 - specifically because when validating output the SDK attempts to use normalizeObjectSchema, which returns undefined for anything besides object schemas (including unions, nullable, etc.).

https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/server/mcp.ts#L309

Hi @joshjg thanks for this - could you elaborate on what you mean that has regressed here? If you could file an issue that would be greatly appreciated!

@joshjg

Copy link
Copy Markdown

@felixweinberger Created #1308 with more details.

Sign up for freeto 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.

9 participants

@ksinder@jjn1056@akshaylingamaneni@davidgilbertson@demian85@RahulLanjewar93@joshjg@felixweinberger@pcarleton