What the MCP validator finds
The validator does two jobs. Under Client config it reads an AI client's MCP config the way the client does and lists what would stop a server from loading: servers under a key the client never reads, a command line packed into the command field, a value of the wrong type, a placeholder in another client's syntax, a token written out in full. Under Tool schemas it checks the tools an MCP server lists against the specification: the fields a tool needs, its inputSchema and outputSchema as JSON Schema, the naming rules and the x-mcp-header annotation. Each problem points at its line and column, and for the common mistakes the output shows your input with the fix applied.
It knows the configs of Claude Desktop, Claude Code, Cursor, VS Code, Devin (formerly Windsurf) and OpenAI Codex, including JSON with comments and Codex's TOML, and the tool definitions of MCP revisions 2026-07-28 and 2025-11-25.
How to check an MCP config or tool schemas
- Choose Client config for a client's config file, or Tool schemas for the tools a server lists.
- Paste the config or the tools, or open the file:
claude_desktop_config.json,.mcp.json,mcp.json,mcp_config.jsonorconfig.tomlfor a config; a tool, a list of tools or atools/listresponse for tool schemas. - Choose the client under Client, or leave Auto-detect on. For tools, choose the revision your server follows under Revision.
- Read the problems, errors first. Select one to jump to its place in the input.
- Where the output shows a fixed version, copy it or download it, and use it in place of your file or your server's definitions.
- Restart the client, or reload its MCP servers, so it reads the change.
Examples
Input
{
"servers": {
"filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/Documents"] }
}
}
Output
Line 2, column 3: Claude Desktop reads servers from mcpServers, not servers. Rename servers to mcpServers: servers is VS Code's key. (MCP_WRONG_TOP_LEVEL_KEY)
Input
{
"mcpServers": {
"git": { "command": "uvx mcp-server-git --repository /Users/me/code/app" }
}
}
Output
Line 3, column 25: The command includes its arguments. Put uvx in command and each argument in args; the client runs the command as one program name. (MCP_COMMAND_HAS_ARGS)
Input
{
"name": "get_weather",
"description": "Get the current weather for a city.",
"inputSchema": {
"type": "object",
"properties": {
"city": { "type": "strng" }
},
"required": ["city", "units"]
}
}
Output
Line 7, column 25: In the inputSchema of get_weather, properties.city.type is "strng", which isn't a JSON Schema type. The types are string, number, integer, boolean, object, array and null; a list of them takes any of those. (MCP_JSON_SCHEMA_INVALID)
Line 9, column 26: In the inputSchema of get_weather, required names "units", which properties leaves out. Add it to properties with its type and description, or remove it from required: a model can't fill in a parameter it doesn't see. (MCP_REQUIRED_NOT_IN_PROPERTIES)
Why a client skips a server
Most configs that fail to load have one of a few mistakes:
- The servers are under the wrong key. VS Code reads
servers; the other JSON clients readmcpServers; Codex wants[mcp_servers.<name>]tables. Snippets copied from a README sometimes have no key at all, and VS Code's oldersettings.jsonkept them insidemcp. - The command holds its arguments. A client starts the command as one program, so
"npx -y package"looks for a program with spaces in its name. The program goes incommand, everything after it inargs. - npx waits for an answer. Without
-y, npx asks before it downloads a package, and nobody is there to reply. - A value has the wrong type. Arguments and environment values are strings:
8080without quotes is a number. VS Code alone takes numbers inenv. - The client reads the entry differently. Claude Code treats a server without
typeas local, so aurlalone isn't enough there, and VS Code needstypeon every server.
Which client a config is for
Codex's TOML and VS Code's servers key give their client away. Claude Desktop, Claude Code, Cursor and Devin all use mcpServers, so the validator looks for what only one of them documents: envFile or ${env:NAME} for Cursor, ${NAME} or headersHelper for Claude Code, transport or disabled for Devin. A config with plain local servers fits all four, and the validator then checks only what they share and says so. Choose the client under Client to check everything, including the fields, placeholders and comments that client reads.
Fields each client documents
| Client | Local server | Remote server | Comments |
|---|---|---|---|
| Claude Desktop | command, args, env | Not in this file | Plain JSON |
| Claude Code | type, command, args, env, timeout, alwaysLoad | type, url, headers, headersHelper, oauth, timeout, alwaysLoad | Plain JSON |
| Cursor | type, command, args, env, envFile | type, url, headers, auth | Not described |
| VS Code | type, command, args, cwd, env, envFile, dev, sandboxEnabled, sandbox | type, url, headers, oauth, dev | Read |
| Devin Desktop (formerly Windsurf) | command, args, env, disabled | url, transport, headers, oauthClientId, oauthClientSecret, oauthResource, disabled | Not described |
| OpenAI Codex | command, args, env, env_vars, cwd, experimental_environment, startup_timeout_sec, tool_timeout_sec, enabled, required, enabled_tools, disabled_tools, default_tools_approval_mode, tools | url, auth, bearer_token_env_var, http_headers, env_http_headers, http_headers_helper, scopes, oauth, startup_timeout_sec, tool_timeout_sec, enabled, required, enabled_tools, disabled_tools, default_tools_approval_mode, tools | TOML: # comments |
A field outside this list is not always an error, since clients add settings over time, but a misspelt one is silently ignored: enviroment instead of env leaves the server without its variables. The validator lists such fields as warnings. Comments are fine in VS Code's mcp.json; Claude Desktop's and Claude Code's documentation shows only plain JSON, and Cursor's and Devin's shows comments in examples without describing them.
Placeholders and secrets
A placeholder works only in the syntax its client replaces; in any other, the server receives the text itself. Claude Code reads ${NAME} and ${NAME:-default}, Cursor and VS Code read ${env:NAME}, and VS Code also reads ${input:id} once the id is defined under inputs. Codex reads variables through env_vars, bearer_token_env_var and env_http_headers. Claude Desktop replaces nothing, and Devin only in its OAuth fields.
Values that look like keys or tokens, found by their name or by a known key format, are listed with the way the client keeps them out of the file. The config never leaves your browser.
What a tool definition needs
A tool is an object with a name and an inputSchema; title, description, outputSchema, annotations and icons are optional. Clients call the tool by its name, and the specification asks for 1 to 128 letters, digits, _, - and ., unique within the server: get_weather or admin.tools.list, not get weather. The model reads the description to decide when to call the tool, so the validator flags one that is missing or only a word or two. A tool written for a model API, with input_schema or parameters, needs its schema renamed to inputSchema.
Tool schemas and JSON Schema
Tool arguments are always one JSON object, so an inputSchema needs "type": "object" at its top, even for a tool without parameters: { "type": "object", "additionalProperties": false } takes only an empty object. Schemas are JSON Schema 2020-12 unless their $schema names another dialect. The validator checks 2020-12 and draft-07 schemas against their official meta-schemas, so a misspelt type, a required that isn't a list or a pattern that isn't a valid regular expression shows up at its place. It also flags a name in required that properties leaves out, since the model never sees what to put there.
Header parameters with x-mcp-header
Since 2026-07-28, a parameter's schema can carry x-mcp-header to send its value as an HTTP header, Mcp-Param-{name}, on Streamable HTTP, so a gateway can route a call without reading its body. A client must leave out a tool whose annotation breaks the rules: the name is an HTTP token without spaces, different from the tool's other header names ignoring case, on a string, integer or boolean parameter reached from the top through properties alone, not inside items, anyOf or $defs. Header values travel in the open, so keep keys, tokens and personal data out of them.
Which MCP revision to choose
Choose the revision your server's SDK implements. 2026-07-28 made every tools/list result carry resultType, ttlMs and cacheScope, added x-mcp-header, moved tasks and the tools' execution field into an extension, and let an outputSchema be any JSON Schema. Under 2025-11-25 an outputSchema is an object schema with "type": "object", like an inputSchema, and each schema under properties is an object rather than true or false.
Suggested fixes
A few mistakes have a fix the validator can make without guessing. In a config, a wrong root key is renamed, servers written at the top are put inside the client's key, and a command line is split into command and args, with -y added for npx. In tool schemas, an inputSchema without a type gets "type": "object", and so does an outputSchema under 2025-11-25. The output shows the whole input with these changes, in its own indentation and with its comments; your input stays as it is. Other problems need a decision, such as which value a secret should come from, so they are listed with a hint instead. Codex's TOML gets hints only.
Options
| Option | Values | Default | When to change it |
|---|---|---|---|
| Client | Auto-detect, Claude Desktop, Claude Code, Cursor, VS Code, Devin (Windsurf), Codex | Auto-detect | Client config: the client whose rules apply. Auto-detect tells it from the config where it can, and remembers your last choice. |
| Revision | 2026-07-28 (latest), 2025-11-25 | 2026-07-28 (latest) | Tool schemas: the MCP specification revision the tools are checked against. |
Common errors and fixes
| Code | What it means | How to fix it |
|---|---|---|
MCP_Error | The servers are under a key the client doesn't read, at the top of the file without a key, or in VS Code's settings.json section. | Use the fixed config, or move the servers under mcpServers (servers for VS Code). |
MCP_Warning | The config fits several clients, so only the rules they share were checked. | Choose the client under Client. |
MCP_Error | A local server has no command or a remote one no url; in Claude Code, a url without a type counts as local. | Add the command, the url, or "type": "http" for Claude Code. |
MCP_Error | A server has both a command and a url. | Delete one of them: a command makes the server local, a url makes it remote. |
MCP_Warning | The command field holds the whole command line. | Use the fixed config, or keep the program in command and the rest in args. |
MCP_Warning | npx would ask for confirmation before downloading the package. | Add -y as the first argument. |
MCP_Error | args is not a list, or holds a number, true or an object. | Write each argument as a string in quotes, as in ["--port", "8080"]. |
MCP_Error | An environment value isn't a string; only VS Code takes numbers and null. | Put the value in quotes. |
MCP_Error | A field has the wrong type or value, VS Code's type is missing, or an ${input:} id isn't defined. | Follow the hint: the problem names what the client expects. |
MCP_Warning | The client's documentation doesn't name this field for this kind of server; often a typo. | Check the spelling against the fields table above. |
MCP_Error | The url isn't an http:// or https:// address. | Copy the endpoint again from the server's documentation. |
MCP_Warning | The url sends requests with http:// to another computer, without encryption. | Use https://, unless the server is on a network you trust. |
MCP_Warning | The server uses SSE, which MCP replaced with Streamable HTTP. | Switch to the server's Streamable HTTP endpoint, often /mcp. |
MCP_Warning | A placeholder is in a syntax this client doesn't replace. | Write it the way the hint shows, or put the value in another way the client reads. |
MCP_Warning | A key or token is written into the file. | Read it from the environment or an input, as the hint shows for your client. |
MCP_Warning | The file has comments or trailing commas, and the client's documentation shows plain JSON. | Remove the comment or the comma. |
MCP_Error | The server's name has characters some clients or commands refuse, or Claude Code keeps it for itself. | Rename it with letters, numbers, - and _. |
MCP_Warning | The client's file doesn't take this kind of server: Claude Desktop starts local servers only. | Add a remote server to Claude Desktop as a custom connector. |
MCP_Error | Codex's config.toml can't be read. | Fix the line shown: a missing quote, bracket or =, or a table written twice. |
MCP_Error | The input is in another format than the client's, or isn't a tool, a list of tools or a tools/list response. | Paste the client's own file, or the server's tools as JSON. |
MCP_Error | A tool, or the tools/list result around it, doesn't match the revision's schema: a missing name or inputSchema, a value of the wrong type. | Follow the hint; for a result of an older server, choose its revision under Revision. |
MCP_Error | An inputSchema has no type or another type than object; under 2025-11-25, an outputSchema too. | Use the fixed tools, or put "type": "object" at the top of the schema. |
MCP_Error | The inputSchema or outputSchema breaks its JSON Schema meta-schema, or is nested too deep to check. | Correct the keyword at the place shown, as the hint describes. |
MCP_Warning | The schema's $schema names a dialect other than 2020-12 or draft-07, so the validator doesn't check it. | Remove $schema to use 2020-12, MCP's default, or write the official URI of the dialect. |
MCP_Warning | The tool's name is empty, longer than 128 characters, or has a character beyond letters, digits, _, - and . | Rename the tool, as the hint suggests. |
MCP_Warning | The tool has no description, or one of a word or two. | Describe what the tool does and when the model should call it. |
MCP_Warning | required names a parameter that properties doesn't define. | Define the parameter, or remove its name from required. |
MCP_Error | Two tools in the list have the same name. | Rename one of them, or remove the copy. |
MCP_Warning | An x-mcp-header breaks the 2026-07-28 rules, or would send a secret as a header. | Correct the header name or the parameter's type, or remove the annotation. |
JSON_Error | The same key appears twice in one object, such as one server name written twice. | Keep one of them: the client reads only the last. |
JSON_Note | A number in a tool schema has more digits than a JavaScript number keeps, so the checks compare the nearest one. | Nothing to fix for the check; your text stays as written. |