MCP Server Configuration Linting Specification

June 10, 2026 · View on GitHub

Version: 1.0.0-draft Date: 2026-04-07 MCP Spec Compatibility: 2025-11-25 (Streamable HTTP) Maintained by: Yaw Labs / ctxlint License: CC BY 4.0


What is this?

MCP server configuration files (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, etc.) define which tools an AI agent can access. They are a context interface — alongside instruction files like CLAUDE.md and .cursorrules, they shape what an agent knows and can do.

This specification defines a standard set of lint rules for validating MCP server configurations across all major AI coding clients. It is tool-agnostic: any linter, IDE extension, CI check, or AI agent can implement these rules.

The specification includes:

  • A complete reference of MCP config file locations, formats, and client-specific behaviors
  • 29 lint rules organized into 8 categories with defined severities
  • A machine-readable rule catalog (mcp-config-lint-rules.json)
  • Auto-fix definitions for rules that support automated correction

Reference implementation: ctxlint (v0.4.0+)


This spec is part of a family of open specifications maintained by Yaw Labs for MCP tooling:

SpecScopeInput
mcp-config-lint (this spec)Static analysis of MCP client config files.cursor/mcp.json, .vscode/mcp.json, .mcp.json, etc.
mcp-complianceRuntime testing of live MCP serversA live server URL + transport

The two are complementary, not overlapping. mcp-config-lint catches problems before deploy by reading JSON on disk; mcp-compliance catches problems after deploy by speaking the protocol to a running server. A production setup typically runs both.

Both specs target MCP spec version 2025-11-25 and ship machine-readable rule catalogs with stable rule IDs.


Table of contents


1. MCP Config Landscape Reference

This section documents the full MCP server configuration landscape as of April 2026. Implementors should treat this as the authoritative cross-client reference for file locations, formats, and behaviors.

1.1 Config format

Every MCP config file is a JSON object with a root key containing named server entries. Each server entry describes how the client connects to one MCP server.

There are two active transport types:

stdio — the client launches a local subprocess and communicates over stdin/stdout using JSON-RPC:

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "@example/mcp-server"],
      "env": { "DEBUG": "true" }
    }
  }
}

Streamable HTTP — the client connects to a remote URL over HTTP:

{
  "mcpServers": {
    "my-server": {
      "type": "http",
      "url": "https://my-server.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${API_KEY}"
      }
    }
  }
}

SSE (Server-Sent Events) — deprecated as of the March 2025 MCP spec update. Uses "type": "sse". Still supported by most clients but should be migrated to Streamable HTTP.

1.2 Server entry fields

FieldTypeTransportRequiredDescription
type"stdio" | "http" | "sse"AllNoTransport protocol. Defaults to stdio if command is present.
commandstringstdioYesExecutable to launch as a subprocess.
argsstring[]stdioNoArguments passed to the command.
envRecord<string, string>stdioNoEnvironment variables for the subprocess.
urlstringhttp, sseYesRemote endpoint URL.
headersRecord<string, string>http, sseNoHTTP headers sent with every request.
disabledbooleanAllNoWhether the server is disabled. (Cline-specific)
autoApprovestring[]AllNoTool names to auto-approve without user confirmation. (Cline-specific)
timeoutnumber (ms)AllNoMax response wait time. Default: 60000. (Amazon Q-specific)
oauthobjecthttpNoOAuth 2.0 configuration. (Claude Code-specific)
headersHelperstringhttpNoShell command that outputs JSON headers to stdout. (Claude Code-specific)

1.3 File locations by client

Project-level configs

These live relative to the project root and are typically committed to version control.

File pathClientRoot keyNotes
.mcp.jsonClaude CodemcpServersThe universal project-level convention.
.cursor/mcp.jsonCursormcpServers
.vscode/mcp.jsonVS Code / GitHub CopilotserversOnly client that uses servers instead of mcpServers.
.amazonq/mcp.jsonAmazon Q DevelopermcpServersServer names must be unique across project + global.
.continue/mcpServers/*.jsonContinue.devvariesAccepts config files from any client format.

User/global-level configs

These are user-specific and not committed to version control.

File pathClientRoot keyPlatform
~/.claude.jsonClaude CodemcpServersAll
~/.claude/settings.jsonClaude CodemcpServersAll
~/.cursor/mcp.jsonCursormcpServersAll
~/Library/Application Support/Claude/claude_desktop_config.jsonClaude DesktopmcpServersmacOS
%APPDATA%\Claude\claude_desktop_config.jsonClaude DesktopmcpServersWindows
~/.codeium/windsurf/mcp_config.jsonWindsurfmcpServersAll
~/.aws/amazonq/mcp.jsonAmazon QmcpServersAll
VS Code globalStorage saoudrizwan.claude-dev/settings/cline_mcp_settings.jsonClinemcpServersAll

1.4 Environment variable syntax

Different clients use different syntax for referencing environment variables in config values.

ClientSyntaxDefault value supportExample
Claude Code${VAR}${VAR:-default}${API_KEY}
Cursor${env:VAR}No${env:API_KEY}
Continue.dev${{ secrets.VAR }}No${{ secrets.API_KEY }}
Windsurf${env:VAR}No${env:API_KEY}
Claude DesktopNot supportedN/ALiteral values only
Amazon QNot supportedN/ALiteral values only

Env var expansion applies to command, args, env, url, and headers fields (where supported).

1.5 Override precedence

When the same server name exists at multiple scopes, the most specific scope wins.

Claude Code (three-tier):

  1. Local (highest) — per-user, per-project overrides in ~/.claude.json under a project path key
  2. Project.mcp.json at the repo root
  3. User (lowest) — ~/.claude.json top-level mcpServers

Cursor: project .cursor/mcp.json overrides global ~/.cursor/mcp.json.

Amazon Q: workspace .amazonq/mcp.json overrides global ~/.aws/amazonq/mcp.json. Server names must be unique across both.

VS Code: workspace .vscode/mcp.json overrides user-level configuration.

Windsurf, Cline: Single global config. No override behavior.

1.6 Platform-specific behaviors

Windows + npx (stdio): On native Windows (not WSL), npx commands must be wrapped with cmd /c:

{
  "command": "cmd",
  "args": ["/c", "npx", "-y", "@example/mcp-server"]
}

Without this wrapper, the subprocess fails to spawn. This is the most common Windows MCP config issue.

Claude.ai custom connectors: Only support remote MCP servers over Streamable HTTP. No stdio support — browsers cannot launch local subprocesses. stdio-only servers must be hosted remotely to be used with Claude.ai.


2. Lint Rules

29 rules organized into 8 categories. Each rule has a unique ID, severity level, trigger condition, and message template.

Severity levels:

  • error — the config is broken or has a security issue. Should fail CI.
  • warning — the config has a likely problem. May or may not fail CI depending on strictness.
  • info — the config has a potential improvement. Never fails CI.

2.1 mcp-schema — structural validation

Validates that the config file is well-formed JSON with the correct structure for its target client.

Rule IDSeverityTriggerMessage
mcp-schema/invalid-jsonerrorFile is not valid JSONMCP config is not valid JSON: {parseError}
mcp-schema/wrong-root-keyerrorRoot key doesn't match expected key for the client{file} must use "{expected}" as root key, not "{actual}"
mcp-schema/missing-root-keyerrorNo recognized root key (mcpServers or servers)MCP config has no "{expected}" key
mcp-schema/missing-commanderrorstdio server has no command fieldServer "{name}" has no "command" field
mcp-schema/missing-urlerrorhttp/sse server has no url fieldServer "{name}" has no "url" field
mcp-schema/no-name-fielderrorA server entry's key (its name) is the empty stringServer name cannot be empty
mcp-schema/unknown-transportwarningTransport cannot be classified: a type outside stdio/http/sse, a non-string type, or an entry with neither command nor urlServer "{name}" has unknown transport type "{type}" (or, with no classifiable fields at all: Server "{name}" has no recognizable transport — expected "command", "url", or a valid "type")
mcp-schema/ambiguous-transportwarningServer has both command and url fieldsServer "{name}" has both "command" and "url" — transport is ambiguous
mcp-schema/empty-serversinfoRoot key exists but contains no server entriesMCP config has no server entries

Auto-fixable: wrong-root-key — rename the root key to match the expected key.

2.2 mcp-security — hardcoded secrets

Detects secrets committed to version control in MCP config files. The three secret rules (hardcoded-bearer, hardcoded-api-key, secret-in-url) only flag issues in git-tracked files — an untracked config leaks nothing to teammates. mcp-security/http-no-tls is a transport concern, independent of version control, and fires regardless of git tracking. When the tracked status cannot be determined at all (git unavailable or failing, as opposed to a determined "untracked"), the linter says so via mcp-security/secret-scan-skipped instead of silently passing a possibly-tracked file.

Rule IDSeverityTriggerMessage
mcp-security/hardcoded-bearererrorAuthorization header contains a literal Bearer token (not an env var reference) in a git-tracked fileServer "{name}" has a hardcoded Bearer token in a git-tracked file
mcp-security/hardcoded-api-keyerrorHeader or env value matches known API key patterns (or the high-entropy heuristic below) in a git-tracked fileServer "{name}" has a hardcoded API key in a git-tracked file
mcp-security/secret-in-urlerrorURL contains query params that look like secrets (?key=, ?token=, ?api_key=) in a git-tracked fileServer "{name}" has a secret in the URL query string
mcp-security/secret-scan-skippedinfoGit-tracked status could not be determined (git unavailable/failing — not merely untracked), so the three git-gated secret rules were skippedCould not determine git-tracked status of {file}; hardcoded-secret rules were skipped
mcp-security/http-no-tlswarningURL uses http:// for a non-loopback target (loopback = localhost, [::1], 127.0.0.0/8)Server "{name}" uses HTTP without TLS

Known API key patterns:

sk-ant-[A-Za-z0-9_-]{20,}      # Anthropic
sk-proj-[A-Za-z0-9_-]{20,}     # OpenAI project-scoped
sk-[a-zA-Z0-9]{20,}            # OpenAI classic / generic (alphanumeric-only:
                               # [-_] would swallow kebab-case identifiers)
ghp_[a-zA-Z0-9]{36}            # GitHub personal access token
ghu_[a-zA-Z0-9]{36}            # GitHub user token
github_pat_[a-zA-Z0-9_]{80,}   # GitHub fine-grained PAT
xoxb-[0-9]{10,}                # Slack bot token
xoxp-[0-9]{10,}                # Slack user token
AKIA[0-9A-Z]{16}               # AWS access key ID
AGE-SECRET-KEY-1[a-zA-Z0-9]+   # age encryption secret key
glpat-[a-zA-Z0-9_\-]{20}       # GitLab personal access token
sq0atp-[a-zA-Z0-9_\-]{22}      # Square access token

High-entropy heuristic: additionally flag an env value > 20 characters that is entirely alphanumeric/base64 characters, is not an env var reference (${...}, ${{ ... }}), AND whose variable name contains a secret-suggesting keyword (KEY, TOKEN, SECRET, PASSWORD, AUTH, CREDENTIAL, SIGNING, SESSION, COOKIE, ...). The name gate is deliberate: without it, build IDs, commit SHAs, version strings, and feature-flag tokens false-positive.

Auto-fixable: hardcoded-bearer, hardcoded-api-key — replace literal value with an env var reference derived from the server name (e.g., MY_SERVER_API_KEY).

2.3 mcp-commands — stdio command validation

Validates that stdio server commands and file-path arguments are viable.

Rule IDSeverityTriggerMessage
mcp-commands/windows-npx-no-wrappererrorPlatform is Windows and command is npx without cmd /c wrapperServer "{name}": npx requires "cmd /c" wrapper on Windows
mcp-commands/command-not-foundwarningcommand is a relative path (./, ../) that doesn't exist (project-scope configs only)Server "{name}": command "{command}" not found
mcp-commands/args-path-missingwarningAn arg matches a file path pattern and the file doesn't exist (relative paths: project-scope configs only; absolute paths: every scope)Server "{name}": arg "{arg}" looks like a file path but doesn't exist

Notes:

  • windows-npx-no-wrapper should only flag project-level configs, not global configs (the user may be developing cross-platform).
  • args-path-missing should only check args that look like file paths (contain / with a file extension, or start with ./ / ../). Skip npm package names and flags.
  • Do not validate that system commands (npx, node, python) exist on PATH — that is a runtime concern, not a config concern.

Auto-fixable: windows-npx-no-wrapper — rewrite {"command": "npx", "args": [...]} to {"command": "cmd", "args": ["/c", "npx", ...]}.

2.4 mcp-deprecated — deprecated patterns

Flags usage of deprecated MCP transport protocols and patterns.

Rule IDSeverityTriggerMessage
mcp-deprecated/sse-transportwarningServer uses "type": "sse"Server "{name}" uses deprecated SSE transport — use "http" (Streamable HTTP) instead

Auto-fixable: sse-transport — replace "sse" with "http".

2.5 mcp-env — environment variable validation

Validates environment variable references for correctness and client compatibility.

Rule IDSeverityTriggerMessage
mcp-env/wrong-syntaxerrorEnv var reference uses wrong syntax for the target clientServer "{name}": {client} uses {expected}, not {actual}
mcp-env/unset-variableinfoReferenced env var is not set in the current environmentServer "{name}": environment variable "{var}" is not set
mcp-env/empty-env-blockinfoenv object is present but emptyServer "{name}": empty "env" block can be removed

Syntax validation matrix:

Config fileExpected syntaxFlag if found
.mcp.json${VAR}${env:VAR}
.cursor/mcp.json${env:VAR}${VAR} (bare, without env:)
.continue/mcpServers/*.json${{ secrets.VAR }}${VAR} or ${env:VAR}
All others${VAR}

Notes:

  • unset-variable is intentionally info severity. Many env vars are set only in CI, .env files, or shell profiles that aren't available during linting.
  • unset-variable is skipped entirely for Continue configs — their ${{ secrets.VAR }} references resolve from GitHub Actions secrets, not the local environment, so every correct Continue config would false-positive.
  • Scan all string values in command, args, url, headers, and env for env var references.

Auto-fixable: wrong-syntax — rewrite to the correct syntax for the target client.

2.6 mcp-urls — URL validation

Validates remote server URLs for correctness and team usability.

Rule IDSeverityTriggerMessage
mcp-urls/malformed-urlerrorURL is not parseable (after skipping env var placeholders)Server "{name}": invalid URL "{url}"
mcp-urls/localhost-in-project-configwarningURL host is a loopback address (localhost, [::1], 127.0.0.0/8 — the same set http-no-tls exempts) in a project-level configServer "{name}": loopback URL in project config won't work for teammates
mcp-urls/missing-pathinfoURL has no path or just /Server "{name}": URL has no path — most MCP servers expect /mcp

Notes:

  • If the URL contains env var references (${...}), skip malformed-url — it cannot be validated statically.
  • localhost-in-project-config should only flag project-scoped files (committed to version control by convention), not global configs where loopback URLs are expected. Strip IPv6 brackets before classifying the host, and treat the whole 127.0.0.0/8 block as loopback — 127.0.0.2 is just as unreachable for a teammate as 127.0.0.1.

2.7 mcp-consistency — cross-file consistency

Compares MCP configs across multiple files in the same project. This is a cross-file check that runs after all individual configs are parsed.

Rule IDSeverityTriggerMessage
mcp-consistency/same-server-different-configwarningServer with the same name exists in 2+ same-scope config files (project-project or user-user) with different URLs or commands — cross-scope pairs are client precedence, not driftServer "{name}" is configured differently in {file1} and {file2}
mcp-consistency/duplicate-server-namewarningSame server name appears more than once in a single fileDuplicate server name "{name}" in {file} — only the last definition is used
mcp-consistency/missing-from-clientinfoServer exists in .mcp.json but is absent from another client's project config that also existsServer "{name}" is in .mcp.json but missing from {file}

Notes:

  • For same-server-different-config, compare url/command/args. Ignore headers differences (auth tokens intentionally differ per user).
  • missing-from-client is informational only. Teams may intentionally have different server sets per client.

2.8 mcp-redundancy — unnecessary configs

Flags configs that may be unnecessary or stale.

Rule IDSeverityTriggerMessage
mcp-redundancy/disabled-serverinfoServer has "disabled": trueServer "{name}" is disabled — consider removing if no longer needed
mcp-redundancy/identical-across-scopesinfoSame server with identical config at both project and global scopeServer "{name}" is identically configured in {projectFile} and {globalFile}

Notes:

  • The disabled field is Cline-specific (see Section 1.2), but disabled-server fires on any client's config that carries it — a stale "disabled": true is dead weight regardless of which client wrote it.

3. Rule Catalog (machine-readable)

A machine-readable JSON catalog of all rules is available at mcp-config-lint-rules.json.

The catalog enables:

  • AI agents to understand what rules exist and when they apply
  • Tool authors to import rule definitions programmatically
  • CI systems to configure which rules to enable/disable
  • Documentation generators to stay in sync with the rule set

See the JSON file for the full schema.


4. Implementing This Specification

This specification is designed to be implementable by any tool. Here is how the pieces map to a typical linter architecture:

Discovery

Scan for the project-level config files listed in Section 1.3. Optionally scan global/user-level configs when the user opts in (these contain personal data and should not be scanned by default).

Parsing

Parse JSON and normalize into a common structure regardless of which client's format the file uses. Key normalization steps:

  1. Detect the client from the file path
  2. Determine the expected root key (servers for VS Code, mcpServers for all others)
  3. Infer transport type from fields: command present = stdio, url present = http/sse, explicit type field takes precedence
  4. Extract server entries into a uniform shape

Checking

Run per-file checks (schema, security, commands, deprecated, env, urls, redundancy) independently per config file. Run cross-file checks (consistency) after all files are parsed.

Reporting

Rules use the category/rule-id naming convention (e.g., mcp-security/hardcoded-bearer). This maps cleanly to SARIF rule IDs for GitHub Code Scanning integration.

Fixing

Rules marked as auto-fixable should apply surgical string replacements to the JSON file without reformatting the user's style (indentation, trailing commas, key ordering). Validate that the result is still valid JSON after applying fixes.


5. Contributing

This specification is maintained at github.com/YawLabs/ctxlint.

To propose changes:

  • New rules: Open an issue describing the rule, its severity, trigger condition, and which clients it applies to.
  • Client additions: As new MCP clients emerge, submit a PR adding their config file location, root key, and any client-specific behaviors to Section 1.
  • Corrections: If any client behavior documented here is inaccurate, open an issue with evidence (link to client docs, source code, or reproduction steps).

Versioning

This specification follows semver:

  • Patch (1.0.x): Typo fixes, clarifications, no rule changes
  • Minor (1.x.0): New rules added, new clients documented
  • Major (x.0.0): Rules removed or semantics changed in breaking ways