Add JSON Schema 2020-12 conformance test (SEP-1613) (#45)

Add conformance test to validate that MCP servers correctly preserve
JSON Schema 2020-12 keywords ($schema, $defs, additionalProperties)
in tool definitions.

Changes:
- Add JsonSchema2020_12Scenario in src/scenarios/server/json-schema-2020-12.ts
- Add json_schema_2020_12_tool to everything-server with tools/list override
  to return raw JSON Schema instead of Zod-converted schema
- Register scenario in pendingClientScenariosList (requires SDK PR #1135)

The test is added to pending because the client SDK currently strips
the JSON Schema 2020-12 keywords. Once PR #1135 is merged into the SDK,
this scenario can be moved to the active list.
This commit is contained in:
Felix Weinberger 2025-11-20 18:20:06 +00:00 • committed by GitHub
commit f087e44bdb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -13,7 +13,13 @@ import {
ResourceTemplate
} from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { ElicitResultSchema } from '@modelcontextprotocol/sdk/types.js';
import {
ElicitResultSchema,
ListToolsRequestSchema,
type ListToolsResult,
type Tool
} from '@modelcontextprotocol/sdk/types.js';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { z } from 'zod';
import express from 'express';
import cors from 'cors';
@ -35,6 +41,28 @@ const TEST_IMAGE_BASE64 =
const TEST_AUDIO_BASE64 =
'UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAA=';
// SEP-1613: Raw JSON Schema 2020-12 definition for conformance testing
// This schema includes $schema, $defs, and additionalProperties to test
// that SDKs correctly preserve these fields
const JSON_SCHEMA_2020_12_INPUT_SCHEMA = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'object' as const,
$defs: {
address: {
type: 'object',
properties: {
street: { type: 'string' },
city: { type: 'string' }
}
}
},
properties: {
name: { type: 'string' },
address: { $ref: '#/$defs/address' }
},
additionalProperties: false
};
// Function to create a new MCP server instance (one per session)
function createMcpServer() {
const mcpServer = new McpServer(
@ -566,6 +594,40 @@ function createMcpServer() {
}
);
// SEP-1613: JSON Schema 2020-12 conformance test tool
// This tool is registered with a Zod schema for tools/call validation,
// but the tools/list handler (below) returns the raw JSON Schema 2020-12
// definition to test that SDKs preserve $schema, $defs, additionalProperties
mcpServer.registerTool(
'json_schema_2020_12_tool',
{
description:
'Tool with JSON Schema 2020-12 features for conformance testing (SEP-1613)',
inputSchema: {
name: z.string().optional(),
address: z
.object({
street: z.string().optional(),
city: z.string().optional()
})
.optional()
}
},
async (args: {
name?: string;
address?: { street?: string; city?: string };
}) => {
return {
content: [
{
type: 'text',
text: `JSON Schema 2020-12 tool called with: ${JSON.stringify(args)}`
}
]
};
}
);
// Dynamic tool (registered later via timer)
// ===== RESOURCES =====
@ -829,6 +891,80 @@ function createMcpServer() {
}
);
// ===== SEP-1613: Override tools/list to return raw JSON Schema 2020-12 =====
// This override is necessary because registerTool converts Zod schemas to
// JSON Schema without preserving $schema, $defs, and additionalProperties.
// We need to return the raw JSON Schema for our test tool while using the
// SDK's conversion for other tools.
mcpServer.server.setRequestHandler(
ListToolsRequestSchema,
(): ListToolsResult => {
// Access internal registered tools (this is internal SDK API but stable)
const registeredTools = (mcpServer as any)._registeredTools as Record<
string,
{
enabled: boolean;
title?: string;
description?: string;
inputSchema?: any;
outputSchema?: any;
annotations?: any;
_meta?: any;
}
>;
return {
tools: Object.entries(registeredTools)
.filter(([, tool]) => tool.enabled)
.map(([name, tool]): Tool => {
// For our SEP-1613 test tool, return raw JSON Schema 2020-12
if (name === 'json_schema_2020_12_tool') {
return {
name,
description: tool.description,
inputSchema: JSON_SCHEMA_2020_12_INPUT_SCHEMA
};
}
// For other tools, convert Zod to JSON Schema
// Handle different inputSchema formats:
// - undefined/null: use empty object schema
// - Zod schema (has _def): convert directly
// - Raw shape (object with Zod values): wrap in z.object first
let inputSchema: Tool['inputSchema'];
if (!tool.inputSchema) {
inputSchema = { type: 'object' as const, properties: {} };
} else if ('_def' in tool.inputSchema) {
// Already a Zod schema
inputSchema = zodToJsonSchema(tool.inputSchema, {
strictUnions: true
}) as Tool['inputSchema'];
} else if (
typeof tool.inputSchema === 'object' &&
Object.keys(tool.inputSchema).length > 0
) {
// Raw shape with Zod values
inputSchema = zodToJsonSchema(z.object(tool.inputSchema), {
strictUnions: true
}) as Tool['inputSchema'];
} else {
// Empty object or unknown format
inputSchema = { type: 'object' as const, properties: {} };
}
return {
name,
title: tool.title,
description: tool.description,
inputSchema,
annotations: tool.annotations,
_meta: tool._meta
};
})
};
}
);
return mcpServer;
}