@@ -13,6 +13,13 @@ import {
1313 ResourceTemplate
1414} from '@modelcontextprotocol/sdk/server/mcp.js' ;
1515import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' ;
16+ import {
17+ ElicitResultSchema ,
18+ ListToolsRequestSchema ,
19+ type ListToolsResult ,
20+ type Tool
21+ } from '@modelcontextprotocol/sdk/types.js' ;
22+ import { zodToJsonSchema } from 'zod-to-json-schema' ;
1623import { z } from 'zod' ;
1724import express from 'express' ;
1825import cors from 'cors' ;
@@ -34,6 +41,28 @@ const TEST_IMAGE_BASE64 =
3441const TEST_AUDIO_BASE64 =
3542 'UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAA=' ;
3643
44+ // SEP-1613: Raw JSON Schema 2020-12 definition for conformance testing
45+ // This schema includes $schema, $defs, and additionalProperties to test
46+ // that SDKs correctly preserve these fields
47+ const JSON_SCHEMA_2020_12_INPUT_SCHEMA = {
48+ $schema : 'https://json-schema.org/draft/2020-12/schema' ,
49+ type : 'object' as const ,
50+ $defs : {
51+ address : {
52+ type : 'object' ,
53+ properties : {
54+ street : { type : 'string' } ,
55+ city : { type : 'string' }
56+ }
57+ }
58+ } ,
59+ properties : {
60+ name : { type : 'string' } ,
61+ address : { $ref : '#/$defs/address' }
62+ } ,
63+ additionalProperties : false
64+ } ;
65+
3766// Function to create a new MCP server instance (one per session)
3867function createMcpServer ( ) {
3968 const mcpServer = new McpServer (
@@ -369,9 +398,7 @@ function createMcpServer() {
369398 }
370399 }
371400 } ,
372- z
373- . object ( { method : z . literal ( 'elicitation/create' ) } )
374- . passthrough ( ) as any
401+ ElicitResultSchema
375402 ) ;
376403
377404 const elicitResult = result as any ;
@@ -445,9 +472,7 @@ function createMcpServer() {
445472 }
446473 }
447474 } ,
448- z
449- . object ( { method : z . literal ( 'elicitation/create' ) } )
450- . passthrough ( ) as any
475+ ElicitResultSchema
451476 ) ;
452477
453478 const elicitResult = result as any ;
@@ -544,9 +569,7 @@ function createMcpServer() {
544569 }
545570 }
546571 } ,
547- z
548- . object ( { method : z . literal ( 'elicitation/create' ) } )
549- . passthrough ( ) as any
572+ ElicitResultSchema
550573 ) ;
551574
552575 const elicitResult = result as any ;
@@ -571,6 +594,40 @@ function createMcpServer() {
571594 }
572595 ) ;
573596
597+ // SEP-1613: JSON Schema 2020-12 conformance test tool
598+ // This tool is registered with a Zod schema for tools/call validation,
599+ // but the tools/list handler (below) returns the raw JSON Schema 2020-12
600+ // definition to test that SDKs preserve $schema, $defs, additionalProperties
601+ mcpServer . registerTool (
602+ 'json_schema_2020_12_tool' ,
603+ {
604+ description :
605+ 'Tool with JSON Schema 2020-12 features for conformance testing (SEP-1613)' ,
606+ inputSchema : {
607+ name : z . string ( ) . optional ( ) ,
608+ address : z
609+ . object ( {
610+ street : z . string ( ) . optional ( ) ,
611+ city : z . string ( ) . optional ( )
612+ } )
613+ . optional ( )
614+ }
615+ } ,
616+ async ( args : {
617+ name ?: string ;
618+ address ?: { street ?: string ; city ?: string } ;
619+ } ) => {
620+ return {
621+ content : [
622+ {
623+ type : 'text' ,
624+ text : `JSON Schema 2020-12 tool called with: ${ JSON . stringify ( args ) } `
625+ }
626+ ]
627+ } ;
628+ }
629+ ) ;
630+
574631 // Dynamic tool (registered later via timer)
575632
576633 // ===== RESOURCES =====
@@ -834,6 +891,80 @@ function createMcpServer() {
834891 }
835892 ) ;
836893
894+ // ===== SEP-1613: Override tools/list to return raw JSON Schema 2020-12 =====
895+ // This override is necessary because registerTool converts Zod schemas to
896+ // JSON Schema without preserving $schema, $defs, and additionalProperties.
897+ // We need to return the raw JSON Schema for our test tool while using the
898+ // SDK's conversion for other tools.
899+ mcpServer . server . setRequestHandler (
900+ ListToolsRequestSchema ,
901+ ( ) : ListToolsResult => {
902+ // Access internal registered tools (this is internal SDK API but stable)
903+ const registeredTools = ( mcpServer as any ) . _registeredTools as Record <
904+ string ,
905+ {
906+ enabled : boolean ;
907+ title ? : string ;
908+ description ? : string ;
909+ inputSchema ? : any ;
910+ outputSchema ? : any ;
911+ annotations ? : any ;
912+ _meta ? : any ;
913+ }
914+ > ;
915+
916+ return {
917+ tools : Object . entries ( registeredTools )
918+ . filter ( ( [ , tool ] ) => tool . enabled )
919+ . map ( ( [ name , tool ] ) : Tool => {
920+ // For our SEP-1613 test tool, return raw JSON Schema 2020-12
921+ if ( name === 'json_schema_2020_12_tool' ) {
922+ return {
923+ name,
924+ description : tool . description ,
925+ inputSchema : JSON_SCHEMA_2020_12_INPUT_SCHEMA
926+ } ;
927+ }
928+
929+ // For other tools, convert Zod to JSON Schema
930+ // Handle different inputSchema formats:
931+ // - undefined/null: use empty object schema
932+ // - Zod schema (has _def): convert directly
933+ // - Raw shape (object with Zod values): wrap in z.object first
934+ let inputSchema : Tool [ 'inputSchema' ] ;
935+ if ( ! tool . inputSchema ) {
936+ inputSchema = { type : 'object' as const , properties : { } } ;
937+ } else if ( '_def' in tool . inputSchema ) {
938+ // Already a Zod schema
939+ inputSchema = zodToJsonSchema ( tool . inputSchema , {
940+ strictUnions : true
941+ } ) as Tool [ 'inputSchema' ] ;
942+ } else if (
943+ typeof tool . inputSchema === 'object' &&
944+ Object . keys ( tool . inputSchema ) . length > 0
945+ ) {
946+ // Raw shape with Zod values
947+ inputSchema = zodToJsonSchema ( z . object ( tool . inputSchema ) , {
948+ strictUnions : true
949+ } ) as Tool [ 'inputSchema' ] ;
950+ } else {
951+ // Empty object or unknown format
952+ inputSchema = { type : 'object' as const , properties : { } } ;
953+ }
954+
955+ return {
956+ name,
957+ title : tool . title ,
958+ description : tool . description ,
959+ inputSchema,
960+ annotations : tool . annotations ,
961+ _meta : tool . _meta
962+ } ;
963+ } )
964+ } ;
965+ }
966+ ) ;
967+
837968 return mcpServer ;
838969}
839970
0 commit comments