@yawlabs/ctxlint 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pre-commit-hooks.yaml +8 -0
- package/CONTEXT_LINT_SPEC.md +613 -0
- package/MCP_CONFIG_LINT_SPEC.md +396 -0
- package/README.md +159 -18
- package/context-lint-rules.json +415 -0
- package/dist/{chunk-WEYNMCAH.js → chunk-PLYBGRJD.js} +1049 -57
- package/dist/chunk-ZXMDA7VB.js +16 -0
- package/dist/{cli-VYWAONGX.js → cli-XQVUFK2D.js} +120 -25
- package/dist/index.js +3 -3
- package/dist/mcp/chunk-MCKGQKYU.js +15 -0
- package/dist/mcp/server.js +1100 -65
- package/dist/mcp/tiktoken-MWTCLHI2.js +465 -0
- package/dist/{server-7C2IQ7VV.js → server-4SC5VMA7.js} +49 -3
- package/dist/tiktoken-CCNRJMFQ.js +466 -0
- package/mcp-config-lint-rules.json +413 -0
- package/package.json +38 -16
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# MCP Server Configuration Linting Specification
|
|
2
|
+
|
|
3
|
+
**Version:** 1.0.0-draft
|
|
4
|
+
**Date:** 2026-04-07
|
|
5
|
+
**MCP Spec Compatibility:** 2025-11-25 (Streamable HTTP)
|
|
6
|
+
**Maintained by:** [Yaw Labs](https://yaw.sh) / [ctxlint](https://github.com/YawLabs/ctxlint)
|
|
7
|
+
**License:** CC BY 4.0
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What is this?
|
|
12
|
+
|
|
13
|
+
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.
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
The specification includes:
|
|
18
|
+
- A complete reference of MCP config file locations, formats, and client-specific behaviors
|
|
19
|
+
- 23 lint rules organized into 8 categories with defined severities
|
|
20
|
+
- A machine-readable rule catalog ([`mcp-config-lint-rules.json`](./mcp-config-lint-rules.json))
|
|
21
|
+
- Auto-fix definitions for rules that support automated correction
|
|
22
|
+
|
|
23
|
+
**Reference implementation:** [ctxlint](https://github.com/YawLabs/ctxlint) (v0.4.0+)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Table of contents
|
|
28
|
+
|
|
29
|
+
- [1. MCP Config Landscape Reference](#1-mcp-config-landscape-reference)
|
|
30
|
+
- [1.1 Config format](#11-config-format)
|
|
31
|
+
- [1.2 Server entry fields](#12-server-entry-fields)
|
|
32
|
+
- [1.3 File locations by client](#13-file-locations-by-client)
|
|
33
|
+
- [1.4 Environment variable syntax](#14-environment-variable-syntax)
|
|
34
|
+
- [1.5 Override precedence](#15-override-precedence)
|
|
35
|
+
- [1.6 Platform-specific behaviors](#16-platform-specific-behaviors)
|
|
36
|
+
- [2. Lint Rules](#2-lint-rules)
|
|
37
|
+
- [2.1 mcp-schema — structural validation](#21-mcp-schema--structural-validation)
|
|
38
|
+
- [2.2 mcp-security — hardcoded secrets](#22-mcp-security--hardcoded-secrets)
|
|
39
|
+
- [2.3 mcp-commands — stdio command validation](#23-mcp-commands--stdio-command-validation)
|
|
40
|
+
- [2.4 mcp-deprecated — deprecated patterns](#24-mcp-deprecated--deprecated-patterns)
|
|
41
|
+
- [2.5 mcp-env — environment variable validation](#25-mcp-env--environment-variable-validation)
|
|
42
|
+
- [2.6 mcp-urls — URL validation](#26-mcp-urls--url-validation)
|
|
43
|
+
- [2.7 mcp-consistency — cross-file consistency](#27-mcp-consistency--cross-file-consistency)
|
|
44
|
+
- [2.8 mcp-redundancy — unnecessary configs](#28-mcp-redundancy--unnecessary-configs)
|
|
45
|
+
- [3. Rule Catalog (machine-readable)](#3-rule-catalog-machine-readable)
|
|
46
|
+
- [4. Implementing This Specification](#4-implementing-this-specification)
|
|
47
|
+
- [5. Contributing](#5-contributing)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 1. MCP Config Landscape Reference
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
### 1.1 Config format
|
|
56
|
+
|
|
57
|
+
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.
|
|
58
|
+
|
|
59
|
+
There are two active transport types:
|
|
60
|
+
|
|
61
|
+
**stdio** — the client launches a local subprocess and communicates over stdin/stdout using JSON-RPC:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"my-server": {
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": ["-y", "@example/mcp-server"],
|
|
69
|
+
"env": { "DEBUG": "true" }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Streamable HTTP** — the client connects to a remote URL over HTTP:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"my-server": {
|
|
81
|
+
"type": "http",
|
|
82
|
+
"url": "https://my-server.example.com/mcp",
|
|
83
|
+
"headers": {
|
|
84
|
+
"Authorization": "Bearer ${API_KEY}"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**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.
|
|
92
|
+
|
|
93
|
+
### 1.2 Server entry fields
|
|
94
|
+
|
|
95
|
+
| Field | Type | Transport | Required | Description |
|
|
96
|
+
|---|---|---|---|---|
|
|
97
|
+
| `type` | `"stdio"` \| `"http"` \| `"sse"` | All | No | Transport protocol. Defaults to `stdio` if `command` is present. |
|
|
98
|
+
| `command` | string | stdio | Yes | Executable to launch as a subprocess. |
|
|
99
|
+
| `args` | string[] | stdio | No | Arguments passed to the command. |
|
|
100
|
+
| `env` | Record<string, string> | stdio | No | Environment variables for the subprocess. |
|
|
101
|
+
| `url` | string | http, sse | Yes | Remote endpoint URL. |
|
|
102
|
+
| `headers` | Record<string, string> | http, sse | No | HTTP headers sent with every request. |
|
|
103
|
+
| `disabled` | boolean | All | No | Whether the server is disabled. (Cline-specific) |
|
|
104
|
+
| `autoApprove` | string[] | All | No | Tool names to auto-approve without user confirmation. (Cline-specific) |
|
|
105
|
+
| `timeout` | number (ms) | All | No | Max response wait time. Default: 60000. (Amazon Q-specific) |
|
|
106
|
+
| `oauth` | object | http | No | OAuth 2.0 configuration. (Claude Code-specific) |
|
|
107
|
+
| `headersHelper` | string | http | No | Shell command that outputs JSON headers to stdout. (Claude Code-specific) |
|
|
108
|
+
|
|
109
|
+
### 1.3 File locations by client
|
|
110
|
+
|
|
111
|
+
#### Project-level configs
|
|
112
|
+
|
|
113
|
+
These live relative to the project root and are typically committed to version control.
|
|
114
|
+
|
|
115
|
+
| File path | Client | Root key | Notes |
|
|
116
|
+
|---|---|---|---|
|
|
117
|
+
| `.mcp.json` | Claude Code | `mcpServers` | The universal project-level convention. |
|
|
118
|
+
| `.cursor/mcp.json` | Cursor | `mcpServers` | |
|
|
119
|
+
| `.vscode/mcp.json` | VS Code / GitHub Copilot | **`servers`** | Only client that uses `servers` instead of `mcpServers`. |
|
|
120
|
+
| `.amazonq/mcp.json` | Amazon Q Developer | `mcpServers` | Server names must be unique across project + global. |
|
|
121
|
+
| `.continue/mcpServers/*.json` | Continue.dev | varies | Accepts config files from any client format. |
|
|
122
|
+
|
|
123
|
+
#### User/global-level configs
|
|
124
|
+
|
|
125
|
+
These are user-specific and not committed to version control.
|
|
126
|
+
|
|
127
|
+
| File path | Client | Root key | Platform |
|
|
128
|
+
|---|---|---|---|
|
|
129
|
+
| `~/.claude.json` | Claude Code | `mcpServers` | All |
|
|
130
|
+
| `~/.claude/settings.json` | Claude Code | `mcpServers` | All |
|
|
131
|
+
| `~/.cursor/mcp.json` | Cursor | `mcpServers` | All |
|
|
132
|
+
| `~/Library/Application Support/Claude/claude_desktop_config.json` | Claude Desktop | `mcpServers` | macOS |
|
|
133
|
+
| `%APPDATA%\Claude\claude_desktop_config.json` | Claude Desktop | `mcpServers` | Windows |
|
|
134
|
+
| `~/.codeium/windsurf/mcp_config.json` | Windsurf | `mcpServers` | All |
|
|
135
|
+
| `~/.aws/amazonq/mcp.json` | Amazon Q | `mcpServers` | All |
|
|
136
|
+
| VS Code globalStorage `saoudrizwan.claude-dev/settings/cline_mcp_settings.json` | Cline | `mcpServers` | All |
|
|
137
|
+
|
|
138
|
+
### 1.4 Environment variable syntax
|
|
139
|
+
|
|
140
|
+
Different clients use different syntax for referencing environment variables in config values.
|
|
141
|
+
|
|
142
|
+
| Client | Syntax | Default value support | Example |
|
|
143
|
+
|---|---|---|---|
|
|
144
|
+
| Claude Code | `${VAR}` | `${VAR:-default}` | `${API_KEY}` |
|
|
145
|
+
| Cursor | `${env:VAR}` | No | `${env:API_KEY}` |
|
|
146
|
+
| Continue.dev | `${{ secrets.VAR }}` | No | `${{ secrets.API_KEY }}` |
|
|
147
|
+
| Windsurf | `${env:VAR}` | No | `${env:API_KEY}` |
|
|
148
|
+
| Claude Desktop | Not supported | N/A | Literal values only |
|
|
149
|
+
| Amazon Q | Not supported | N/A | Literal values only |
|
|
150
|
+
|
|
151
|
+
Env var expansion applies to `command`, `args`, `env`, `url`, and `headers` fields (where supported).
|
|
152
|
+
|
|
153
|
+
### 1.5 Override precedence
|
|
154
|
+
|
|
155
|
+
When the same server name exists at multiple scopes, the most specific scope wins.
|
|
156
|
+
|
|
157
|
+
**Claude Code** (three-tier):
|
|
158
|
+
1. **Local** (highest) — per-user, per-project overrides in `~/.claude.json` under a project path key
|
|
159
|
+
2. **Project** — `.mcp.json` at the repo root
|
|
160
|
+
3. **User** (lowest) — `~/.claude.json` top-level `mcpServers`
|
|
161
|
+
|
|
162
|
+
**Cursor:** project `.cursor/mcp.json` overrides global `~/.cursor/mcp.json`.
|
|
163
|
+
|
|
164
|
+
**Amazon Q:** workspace `.amazonq/mcp.json` overrides global `~/.aws/amazonq/mcp.json`. Server names must be unique across both.
|
|
165
|
+
|
|
166
|
+
**VS Code:** workspace `.vscode/mcp.json` overrides user-level configuration.
|
|
167
|
+
|
|
168
|
+
**Windsurf, Cline:** Single global config. No override behavior.
|
|
169
|
+
|
|
170
|
+
### 1.6 Platform-specific behaviors
|
|
171
|
+
|
|
172
|
+
**Windows + npx (stdio):** On native Windows (not WSL), `npx` commands must be wrapped with `cmd /c`:
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"command": "cmd",
|
|
176
|
+
"args": ["/c", "npx", "-y", "@example/mcp-server"]
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
Without this wrapper, the subprocess fails to spawn. This is the most common Windows MCP config issue.
|
|
180
|
+
|
|
181
|
+
**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.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 2. Lint Rules
|
|
186
|
+
|
|
187
|
+
23 rules organized into 8 categories. Each rule has a unique ID, severity level, trigger condition, and message template.
|
|
188
|
+
|
|
189
|
+
Severity levels:
|
|
190
|
+
- **error** — the config is broken or has a security issue. Should fail CI.
|
|
191
|
+
- **warning** — the config has a likely problem. May or may not fail CI depending on strictness.
|
|
192
|
+
- **info** — the config has a potential improvement. Never fails CI.
|
|
193
|
+
|
|
194
|
+
### 2.1 mcp-schema — structural validation
|
|
195
|
+
|
|
196
|
+
Validates that the config file is well-formed JSON with the correct structure for its target client.
|
|
197
|
+
|
|
198
|
+
| Rule ID | Severity | Trigger | Message |
|
|
199
|
+
|---|---|---|---|
|
|
200
|
+
| `mcp-schema/invalid-json` | error | File is not valid JSON | `MCP config is not valid JSON: {parseError}` |
|
|
201
|
+
| `mcp-schema/wrong-root-key` | error | Root key doesn't match expected key for the client | `{file} must use "{expected}" as root key, not "{actual}"` |
|
|
202
|
+
| `mcp-schema/missing-root-key` | error | No recognized root key (`mcpServers` or `servers`) | `MCP config has no "{expected}" key` |
|
|
203
|
+
| `mcp-schema/missing-command` | error | stdio server has no `command` field | `Server "{name}": missing "command" field` |
|
|
204
|
+
| `mcp-schema/missing-url` | error | http/sse server has no `url` field | `Server "{name}": missing "url" field` |
|
|
205
|
+
| `mcp-schema/unknown-transport` | warning | `type` field is not `stdio`, `http`, or `sse` | `Server "{name}": unknown transport type "{type}"` |
|
|
206
|
+
| `mcp-schema/ambiguous-transport` | warning | Server has both `command` and `url` fields | `Server "{name}": has both "command" and "url" — transport is ambiguous` |
|
|
207
|
+
| `mcp-schema/empty-servers` | info | Root key exists but contains no server entries | `MCP config has no server entries` |
|
|
208
|
+
|
|
209
|
+
**Auto-fixable:** `wrong-root-key` — rename the root key to match the expected key.
|
|
210
|
+
|
|
211
|
+
### 2.2 mcp-security — hardcoded secrets
|
|
212
|
+
|
|
213
|
+
Detects secrets committed to version control in MCP config files. Only flags issues in git-tracked files.
|
|
214
|
+
|
|
215
|
+
| Rule ID | Severity | Trigger | Message |
|
|
216
|
+
|---|---|---|---|
|
|
217
|
+
| `mcp-security/hardcoded-bearer` | error | `Authorization` header contains a literal Bearer token (not an env var reference) in a git-tracked file | `Server "{name}": hardcoded Bearer token in a git-tracked file` |
|
|
218
|
+
| `mcp-security/hardcoded-api-key` | error | Header or env value matches known API key patterns in a git-tracked file | `Server "{name}": possible API key in a git-tracked file` |
|
|
219
|
+
| `mcp-security/secret-in-url` | error | URL contains query params that look like secrets (`?key=`, `?token=`, `?api_key=`) in a git-tracked file | `Server "{name}": possible secret in URL query string` |
|
|
220
|
+
| `mcp-security/http-no-tls` | warning | URL uses `http://` for non-localhost target | `Server "{name}": URL uses HTTP without TLS` |
|
|
221
|
+
|
|
222
|
+
**Known API key patterns:**
|
|
223
|
+
```
|
|
224
|
+
sk-[a-zA-Z0-9]{20,} # OpenAI / generic
|
|
225
|
+
ghp_[a-zA-Z0-9]{36} # GitHub personal access token
|
|
226
|
+
ghu_[a-zA-Z0-9]{36} # GitHub user token
|
|
227
|
+
github_pat_[a-zA-Z0-9_]{80,} # GitHub fine-grained PAT
|
|
228
|
+
xoxb-[0-9]{10,} # Slack bot token
|
|
229
|
+
xoxp-[0-9]{10,} # Slack user token
|
|
230
|
+
AKIA[0-9A-Z]{16} # AWS access key ID
|
|
231
|
+
glpat-[a-zA-Z0-9_\-]{20} # GitLab personal access token
|
|
232
|
+
sq0atp-[a-zA-Z0-9_\-]{22} # Square access token
|
|
233
|
+
shpat_[a-fA-F0-9]{32} # Shopify admin API token
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Also flag any string value > 20 characters that is entirely alphanumeric or base64 characters AND is not an env var reference (`${...}`, `${{ ... }}`).
|
|
237
|
+
|
|
238
|
+
**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`).
|
|
239
|
+
|
|
240
|
+
### 2.3 mcp-commands — stdio command validation
|
|
241
|
+
|
|
242
|
+
Validates that stdio server commands and file-path arguments are viable.
|
|
243
|
+
|
|
244
|
+
| Rule ID | Severity | Trigger | Message |
|
|
245
|
+
|---|---|---|---|
|
|
246
|
+
| `mcp-commands/windows-npx-no-wrapper` | error | Platform is Windows and `command` is `npx` without `cmd /c` wrapper | `Server "{name}": npx requires "cmd /c" wrapper on Windows` |
|
|
247
|
+
| `mcp-commands/command-not-found` | warning | `command` is a relative path (`./`, `../`) that doesn't exist | `Server "{name}": command "{command}" not found` |
|
|
248
|
+
| `mcp-commands/args-path-missing` | warning | An arg matches a file path pattern and the file doesn't exist | `Server "{name}": arg "{arg}" references a missing file` |
|
|
249
|
+
|
|
250
|
+
**Notes:**
|
|
251
|
+
- `windows-npx-no-wrapper` should only flag project-level configs, not global configs (the user may be developing cross-platform).
|
|
252
|
+
- `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.
|
|
253
|
+
- Do not validate that system commands (`npx`, `node`, `python`) exist on PATH — that is a runtime concern, not a config concern.
|
|
254
|
+
|
|
255
|
+
**Auto-fixable:** `windows-npx-no-wrapper` — rewrite `{"command": "npx", "args": [...]}` to `{"command": "cmd", "args": ["/c", "npx", ...]}`.
|
|
256
|
+
|
|
257
|
+
### 2.4 mcp-deprecated — deprecated patterns
|
|
258
|
+
|
|
259
|
+
Flags usage of deprecated MCP transport protocols and patterns.
|
|
260
|
+
|
|
261
|
+
| Rule ID | Severity | Trigger | Message |
|
|
262
|
+
|---|---|---|---|
|
|
263
|
+
| `mcp-deprecated/sse-transport` | warning | Server uses `"type": "sse"` | `Server "{name}": SSE transport is deprecated — use "http" (Streamable HTTP)` |
|
|
264
|
+
|
|
265
|
+
**Auto-fixable:** `sse-transport` — replace `"sse"` with `"http"`.
|
|
266
|
+
|
|
267
|
+
### 2.5 mcp-env — environment variable validation
|
|
268
|
+
|
|
269
|
+
Validates environment variable references for correctness and client compatibility.
|
|
270
|
+
|
|
271
|
+
| Rule ID | Severity | Trigger | Message |
|
|
272
|
+
|---|---|---|---|
|
|
273
|
+
| `mcp-env/wrong-syntax` | error | Env var reference uses wrong syntax for the target client | `Server "{name}": {client} uses {expected}, not {actual}` |
|
|
274
|
+
| `mcp-env/unset-variable` | info | Referenced env var is not set in the current environment | `Server "{name}": environment variable "{var}" is not set` |
|
|
275
|
+
| `mcp-env/empty-env-block` | info | `env` object is present but empty | `Server "{name}": empty "env" block can be removed` |
|
|
276
|
+
|
|
277
|
+
**Syntax validation matrix:**
|
|
278
|
+
|
|
279
|
+
| Config file | Expected syntax | Flag if found |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| `.mcp.json` | `${VAR}` | `${env:VAR}` |
|
|
282
|
+
| `.cursor/mcp.json` | `${env:VAR}` | `${VAR}` (bare, without `env:`) |
|
|
283
|
+
| `.continue/mcpServers/*.json` | `${{ secrets.VAR }}` | `${VAR}` or `${env:VAR}` |
|
|
284
|
+
| All others | `${VAR}` | — |
|
|
285
|
+
|
|
286
|
+
**Notes:**
|
|
287
|
+
- `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.
|
|
288
|
+
- Scan all string values in `command`, `args`, `url`, `headers`, and `env` for env var references.
|
|
289
|
+
|
|
290
|
+
**Auto-fixable:** `wrong-syntax` — rewrite to the correct syntax for the target client.
|
|
291
|
+
|
|
292
|
+
### 2.6 mcp-urls — URL validation
|
|
293
|
+
|
|
294
|
+
Validates remote server URLs for correctness and team usability.
|
|
295
|
+
|
|
296
|
+
| Rule ID | Severity | Trigger | Message |
|
|
297
|
+
|---|---|---|---|
|
|
298
|
+
| `mcp-urls/malformed` | error | URL is not parseable (after skipping env var placeholders) | `Server "{name}": invalid URL` |
|
|
299
|
+
| `mcp-urls/localhost-in-project-config` | warning | `localhost` or `127.0.0.1` URL in a git-tracked project config | `Server "{name}": localhost URL in project config won't work for teammates` |
|
|
300
|
+
| `mcp-urls/missing-path` | info | URL has no path or just `/` | `Server "{name}": URL has no path — most MCP servers expect /mcp` |
|
|
301
|
+
|
|
302
|
+
**Notes:**
|
|
303
|
+
- If the URL contains env var references (`${...}`), skip `malformed` — it cannot be validated statically.
|
|
304
|
+
- `localhost-in-project-config` should only flag project-scoped files, not global configs where localhost is expected.
|
|
305
|
+
|
|
306
|
+
### 2.7 mcp-consistency — cross-file consistency
|
|
307
|
+
|
|
308
|
+
Compares MCP configs across multiple files in the same project. This is a cross-file check that runs after all individual configs are parsed.
|
|
309
|
+
|
|
310
|
+
| Rule ID | Severity | Trigger | Message |
|
|
311
|
+
|---|---|---|---|
|
|
312
|
+
| `mcp-consistency/same-server-different-config` | warning | Server with the same name exists in 2+ config files with different URLs or commands | `Server "{name}" is configured differently in {file1} and {file2}` |
|
|
313
|
+
| `mcp-consistency/duplicate-server-name` | warning | Same server name appears more than once in a single file | `Duplicate server name "{name}" in {file} — only the last definition is used` |
|
|
314
|
+
| `mcp-consistency/missing-from-client` | info | Server exists in `.mcp.json` but is absent from another client's project config that also exists | `Server "{name}" is in .mcp.json but missing from {file}` |
|
|
315
|
+
|
|
316
|
+
**Notes:**
|
|
317
|
+
- For `same-server-different-config`, compare `url`/`command`/`args`. Ignore `headers` differences (auth tokens intentionally differ per user).
|
|
318
|
+
- `missing-from-client` is informational only. Teams may intentionally have different server sets per client.
|
|
319
|
+
|
|
320
|
+
### 2.8 mcp-redundancy — unnecessary configs
|
|
321
|
+
|
|
322
|
+
Flags configs that may be unnecessary or stale.
|
|
323
|
+
|
|
324
|
+
| Rule ID | Severity | Trigger | Message |
|
|
325
|
+
|---|---|---|---|
|
|
326
|
+
| `mcp-redundancy/disabled-server` | info | Server has `"disabled": true` | `Server "{name}" is disabled — consider removing if no longer needed` |
|
|
327
|
+
| `mcp-redundancy/identical-across-scopes` | info | Same server with identical config at both project and global scope | `Server "{name}" is identically configured in {projectFile} and {globalFile}` |
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## 3. Rule Catalog (machine-readable)
|
|
332
|
+
|
|
333
|
+
A machine-readable JSON catalog of all rules is available at [`mcp-config-lint-rules.json`](./mcp-config-lint-rules.json).
|
|
334
|
+
|
|
335
|
+
The catalog enables:
|
|
336
|
+
- AI agents to understand what rules exist and when they apply
|
|
337
|
+
- Tool authors to import rule definitions programmatically
|
|
338
|
+
- CI systems to configure which rules to enable/disable
|
|
339
|
+
- Documentation generators to stay in sync with the rule set
|
|
340
|
+
|
|
341
|
+
See the JSON file for the full schema.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## 4. Implementing This Specification
|
|
346
|
+
|
|
347
|
+
This specification is designed to be implementable by any tool. Here is how the pieces map to a typical linter architecture:
|
|
348
|
+
|
|
349
|
+
### Discovery
|
|
350
|
+
|
|
351
|
+
Scan for the project-level config files listed in [Section 1.3](#13-file-locations-by-client). Optionally scan global/user-level configs when the user opts in (these contain personal data and should not be scanned by default).
|
|
352
|
+
|
|
353
|
+
### Parsing
|
|
354
|
+
|
|
355
|
+
Parse JSON and normalize into a common structure regardless of which client's format the file uses. Key normalization steps:
|
|
356
|
+
1. Detect the client from the file path
|
|
357
|
+
2. Determine the expected root key (`servers` for VS Code, `mcpServers` for all others)
|
|
358
|
+
3. Infer transport type from fields: `command` present = stdio, `url` present = http/sse, explicit `type` field takes precedence
|
|
359
|
+
4. Extract server entries into a uniform shape
|
|
360
|
+
|
|
361
|
+
### Checking
|
|
362
|
+
|
|
363
|
+
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.
|
|
364
|
+
|
|
365
|
+
### Reporting
|
|
366
|
+
|
|
367
|
+
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.
|
|
368
|
+
|
|
369
|
+
### Fixing
|
|
370
|
+
|
|
371
|
+
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.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 5. Contributing
|
|
376
|
+
|
|
377
|
+
This specification is maintained at [github.com/YawLabs/ctxlint](https://github.com/YawLabs/ctxlint).
|
|
378
|
+
|
|
379
|
+
To propose changes:
|
|
380
|
+
- **New rules:** Open an issue describing the rule, its severity, trigger condition, and which clients it applies to.
|
|
381
|
+
- **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.
|
|
382
|
+
- **Corrections:** If any client behavior documented here is inaccurate, open an issue with evidence (link to client docs, source code, or reproduction steps).
|
|
383
|
+
|
|
384
|
+
### Versioning
|
|
385
|
+
|
|
386
|
+
This specification follows semver:
|
|
387
|
+
- **Patch** (1.0.x): Typo fixes, clarifications, no rule changes
|
|
388
|
+
- **Minor** (1.x.0): New rules added, new clients documented
|
|
389
|
+
- **Major** (x.0.0): Rules removed or semantics changed in breaking ways
|
|
390
|
+
|
|
391
|
+
### Related specifications and tools
|
|
392
|
+
|
|
393
|
+
- [Model Context Protocol Specification](https://spec.modelcontextprotocol.io/) — the underlying protocol this config format serves
|
|
394
|
+
- [ctxlint](https://github.com/YawLabs/ctxlint) — reference implementation of this specification
|
|
395
|
+
- [mcp-compliance](https://github.com/YawLabs/mcp-compliance) — tests MCP server *behavior* against the protocol spec (complementary to config linting)
|
|
396
|
+
- [mcp.hosting](https://mcp.hosting) — managed MCP server hosting (eliminates many config issues by providing remote endpoints)
|
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
[](https://github.com/YawLabs/ctxlint/stargazers)
|
|
6
6
|
[](https://github.com/YawLabs/ctxlint/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
|
-
**Lint your AI agent context files against your actual codebase.**
|
|
8
|
+
**Lint your AI agent context files and MCP server configs against your actual codebase.** Context linting + MCP config linting. 21+ context formats, 8 MCP clients, auto-fix. Works as a CLI, CI step, pre-commit hook, or MCP server.
|
|
9
9
|
|
|
10
|
-
Your `CLAUDE.md` is lying to your agent. ctxlint catches
|
|
10
|
+
Your `CLAUDE.md` is lying to your agent. Your `.mcp.json` has a hardcoded API key. ctxlint catches both.
|
|
11
11
|
|
|
12
12
|
## Why ctxlint?
|
|
13
13
|
|
|
@@ -19,20 +19,42 @@ Context files rot fast. You rename a file, change a build script, or switch from
|
|
|
19
19
|
- **Token-aware** — shows how much context window your files consume and flags redundant content
|
|
20
20
|
- **Every AI tool** — supports Claude Code, Cursor, Copilot, Windsurf, Gemini, Cline, Aider, and 14 more
|
|
21
21
|
- **Multiple outputs** — text, JSON, and SARIF (GitHub Code Scanning)
|
|
22
|
-
- **MCP server** —
|
|
22
|
+
- **MCP server** — 5 tools for IDE/agent integration with tool annotations for auto-approval
|
|
23
23
|
|
|
24
24
|
## Install
|
|
25
25
|
|
|
26
|
+
Run directly (no install needed):
|
|
27
|
+
|
|
26
28
|
```bash
|
|
27
|
-
|
|
29
|
+
npx @yawlabs/ctxlint
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
### Project install (recommended for teams)
|
|
31
33
|
|
|
32
34
|
```bash
|
|
33
|
-
|
|
35
|
+
npm install -D @yawlabs/ctxlint
|
|
36
|
+
# or
|
|
37
|
+
pnpm add -D @yawlabs/ctxlint
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then add to your `package.json` scripts:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"scripts": {
|
|
45
|
+
"lint:ctx": "ctxlint --strict"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
34
48
|
```
|
|
35
49
|
|
|
50
|
+
### Global install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install -g @yawlabs/ctxlint
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Useful if you want `ctxlint` available in every project without per-project setup.
|
|
57
|
+
|
|
36
58
|
## What It Checks
|
|
37
59
|
|
|
38
60
|
| Check | What it finds |
|
|
@@ -66,10 +88,79 @@ npx @yawlabs/ctxlint
|
|
|
66
88
|
| `.rules` | Zed |
|
|
67
89
|
| `replit.md` | Replit |
|
|
68
90
|
|
|
91
|
+
## MCP Server Config Linting
|
|
92
|
+
|
|
93
|
+
ctxlint also lints MCP server configuration files — the JSON configs that tell AI clients which tools to connect to. These are context interfaces too: they shape what your agent can do.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Lint context files + MCP configs
|
|
97
|
+
npx @yawlabs/ctxlint --mcp
|
|
98
|
+
|
|
99
|
+
# Lint only MCP configs
|
|
100
|
+
npx @yawlabs/ctxlint --mcp-only
|
|
101
|
+
|
|
102
|
+
# Include global/user-level configs (Claude Desktop, Cursor, Windsurf, etc.)
|
|
103
|
+
npx @yawlabs/ctxlint --mcp-global
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### What MCP config files are scanned
|
|
107
|
+
|
|
108
|
+
| File | Client |
|
|
109
|
+
|------|--------|
|
|
110
|
+
| `.mcp.json` | Claude Code (universal project config) |
|
|
111
|
+
| `.cursor/mcp.json` | Cursor |
|
|
112
|
+
| `.vscode/mcp.json` | VS Code / GitHub Copilot |
|
|
113
|
+
| `.amazonq/mcp.json` | Amazon Q Developer |
|
|
114
|
+
| `.continue/mcpServers/*.json` | Continue |
|
|
115
|
+
|
|
116
|
+
With `--mcp-global`, also scans Claude Desktop, Cursor, Windsurf, and Amazon Q global configs.
|
|
117
|
+
|
|
118
|
+
### What MCP config checks catch
|
|
119
|
+
|
|
120
|
+
| Check | What it finds |
|
|
121
|
+
|-------|--------------|
|
|
122
|
+
| **Schema** | Invalid JSON, wrong root key (`servers` vs `mcpServers`), missing required fields |
|
|
123
|
+
| **Security** | Hardcoded API keys and Bearer tokens in git-tracked config files |
|
|
124
|
+
| **Commands** | Missing `cmd /c` wrapper for npx on Windows, broken file paths in args |
|
|
125
|
+
| **Deprecated** | SSE transport usage (deprecated March 2025, use Streamable HTTP) |
|
|
126
|
+
| **Env vars** | Wrong env var syntax for the client (`${VAR}` vs `${env:VAR}` vs `${{ secrets.VAR }}`) |
|
|
127
|
+
| **URLs** | Malformed URLs, localhost in project configs, missing path component |
|
|
128
|
+
| **Consistency** | Same server configured differently across client configs |
|
|
129
|
+
| **Redundancy** | Disabled servers, identical configs at multiple scopes |
|
|
130
|
+
|
|
131
|
+
### Example MCP config output
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
MCP Configs
|
|
135
|
+
.mcp.json
|
|
136
|
+
✗ mcp-security Server "api": hardcoded Bearer token in a git-tracked file
|
|
137
|
+
✗ mcp-deprecated Server "old-svc": SSE transport is deprecated — use "http"
|
|
138
|
+
✓ mcp-schema
|
|
139
|
+
✓ mcp-commands
|
|
140
|
+
.cursor/mcp.json
|
|
141
|
+
✗ mcp-env Server "api": Cursor uses ${env:VAR}, not ${VAR}
|
|
142
|
+
✓ mcp-schema
|
|
143
|
+
.vscode/mcp.json
|
|
144
|
+
✗ mcp-schema .vscode/mcp.json must use "servers" as root key, not "mcpServers"
|
|
145
|
+
|
|
146
|
+
Cross-file
|
|
147
|
+
⚠ Server "api" is configured differently in .mcp.json and .cursor/mcp.json
|
|
148
|
+
ℹ Server "db" is in .mcp.json but missing from .cursor/mcp.json
|
|
149
|
+
|
|
150
|
+
Summary: 3 errors, 2 warnings, 1 info
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### MCP Config Linting Specification
|
|
154
|
+
|
|
155
|
+
The full specification for MCP config linting rules, the cross-client config landscape, and a machine-readable rule catalog are published as open specifications:
|
|
156
|
+
|
|
157
|
+
- **[`MCP_CONFIG_LINT_SPEC.md`](./MCP_CONFIG_LINT_SPEC.md)** — 23 lint rules across 8 categories, the complete client/format reference, and implementation guidance. Tool-agnostic — any linter can implement it.
|
|
158
|
+
- **[`mcp-config-lint-rules.json`](./mcp-config-lint-rules.json)** — Machine-readable rule catalog for programmatic consumption by AI agents, CI systems, and other tools.
|
|
159
|
+
|
|
69
160
|
## Example Output
|
|
70
161
|
|
|
71
162
|
```
|
|
72
|
-
ctxlint v0.
|
|
163
|
+
ctxlint v0.6.0
|
|
73
164
|
|
|
74
165
|
Scanning /Users/you/my-app...
|
|
75
166
|
|
|
@@ -100,16 +191,19 @@ Arguments:
|
|
|
100
191
|
|
|
101
192
|
Options:
|
|
102
193
|
--strict Exit code 1 on any warning or error (for CI)
|
|
103
|
-
--checks <list> Comma-separated
|
|
194
|
+
--checks <list> Comma-separated checks to run (see below)
|
|
104
195
|
--ignore <list> Comma-separated checks to skip
|
|
105
196
|
--fix Auto-fix broken paths using git history and fuzzy matching
|
|
106
197
|
--format <fmt> Output format: text, json, or sarif (default: text)
|
|
107
198
|
--tokens Show token breakdown per file
|
|
108
199
|
--verbose Show passing checks too
|
|
109
|
-
--quiet Suppress all output (exit code only
|
|
200
|
+
--quiet Suppress all output except errors (exit code only)
|
|
110
201
|
--config <path> Path to config file (default: .ctxlintrc in project root)
|
|
111
202
|
--depth <n> Max subdirectory depth to scan (default: 2)
|
|
112
|
-
--mcp
|
|
203
|
+
--mcp Enable MCP config linting alongside context file checks
|
|
204
|
+
--mcp-only Run only MCP config checks, skip context file checks
|
|
205
|
+
--mcp-global Also scan user/global MCP config files (implies --mcp)
|
|
206
|
+
--mcp-server Start the MCP server (for IDE/agent integration)
|
|
113
207
|
-V, --version Output the version number
|
|
114
208
|
-h, --help Display help
|
|
115
209
|
|
|
@@ -117,6 +211,10 @@ Commands:
|
|
|
117
211
|
init Set up a git pre-commit hook
|
|
118
212
|
```
|
|
119
213
|
|
|
214
|
+
**Available checks:** `paths`, `commands`, `staleness`, `tokens`, `redundancy`, `contradictions`, `frontmatter`, `mcp-schema`, `mcp-security`, `mcp-commands`, `mcp-deprecated`, `mcp-env`, `mcp-urls`, `mcp-consistency`, `mcp-redundancy`
|
|
215
|
+
|
|
216
|
+
Passing any `mcp-*` check name implies `--mcp`.
|
|
217
|
+
|
|
120
218
|
## Use in CI
|
|
121
219
|
|
|
122
220
|
```yaml
|
|
@@ -163,7 +261,7 @@ Add to your `.pre-commit-config.yaml`:
|
|
|
163
261
|
```yaml
|
|
164
262
|
repos:
|
|
165
263
|
- repo: https://github.com/yawlabs/ctxlint
|
|
166
|
-
rev: v0.
|
|
264
|
+
rev: v0.6.0
|
|
167
265
|
hooks:
|
|
168
266
|
- id: ctxlint
|
|
169
267
|
```
|
|
@@ -193,9 +291,15 @@ CLI flags override config file settings. Use `--config <path>` to load a config
|
|
|
193
291
|
|
|
194
292
|
## Use as MCP Server
|
|
195
293
|
|
|
196
|
-
ctxlint ships with an MCP server that exposes
|
|
294
|
+
ctxlint ships with an MCP server that exposes five tools (`ctxlint_audit`, `ctxlint_mcp_audit`, `ctxlint_validate_path`, `ctxlint_token_report`, `ctxlint_fix`). All read-only tools declare annotations so MCP clients can skip confirmation dialogs.
|
|
295
|
+
|
|
296
|
+
### With Claude Code
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
claude mcp add ctxlint -- npx -y @yawlabs/ctxlint --mcp-server
|
|
300
|
+
```
|
|
197
301
|
|
|
198
|
-
### With `.mcp.json` (
|
|
302
|
+
### With `.mcp.json` (Claude Code project config, Cursor, Windsurf)
|
|
199
303
|
|
|
200
304
|
Create `.mcp.json` in your project root:
|
|
201
305
|
|
|
@@ -206,7 +310,7 @@ macOS / Linux / WSL:
|
|
|
206
310
|
"mcpServers": {
|
|
207
311
|
"ctxlint": {
|
|
208
312
|
"command": "npx",
|
|
209
|
-
"args": ["-y", "@yawlabs/ctxlint", "--mcp"]
|
|
313
|
+
"args": ["-y", "@yawlabs/ctxlint", "--mcp-server"]
|
|
210
314
|
}
|
|
211
315
|
}
|
|
212
316
|
}
|
|
@@ -219,7 +323,7 @@ Windows:
|
|
|
219
323
|
"mcpServers": {
|
|
220
324
|
"ctxlint": {
|
|
221
325
|
"command": "cmd",
|
|
222
|
-
"args": ["/c", "npx", "-y", "@yawlabs/ctxlint", "--mcp"]
|
|
326
|
+
"args": ["/c", "npx", "-y", "@yawlabs/ctxlint", "--mcp-server"]
|
|
223
327
|
}
|
|
224
328
|
}
|
|
225
329
|
}
|
|
@@ -227,10 +331,34 @@ Windows:
|
|
|
227
331
|
|
|
228
332
|
> **Tip:** This file is safe to commit — it contains no secrets.
|
|
229
333
|
|
|
230
|
-
### With
|
|
334
|
+
### With VS Code / GitHub Copilot
|
|
231
335
|
|
|
232
|
-
|
|
233
|
-
|
|
336
|
+
Add to `.vscode/mcp.json`:
|
|
337
|
+
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"servers": {
|
|
341
|
+
"ctxlint": {
|
|
342
|
+
"command": "npx",
|
|
343
|
+
"args": ["-y", "@yawlabs/ctxlint", "--mcp-server"]
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### With Claude Desktop
|
|
350
|
+
|
|
351
|
+
Add to your Claude Desktop config (`claude_desktop_config.json`):
|
|
352
|
+
|
|
353
|
+
```json
|
|
354
|
+
{
|
|
355
|
+
"mcpServers": {
|
|
356
|
+
"ctxlint": {
|
|
357
|
+
"command": "npx",
|
|
358
|
+
"args": ["-y", "@yawlabs/ctxlint", "--mcp-server"]
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
234
362
|
```
|
|
235
363
|
|
|
236
364
|
## JSON Output
|
|
@@ -241,6 +369,19 @@ npx @yawlabs/ctxlint --format json
|
|
|
241
369
|
|
|
242
370
|
Returns structured JSON with all file results, issues, and summary — useful for building integrations or dashboards.
|
|
243
371
|
|
|
372
|
+
## Specifications
|
|
373
|
+
|
|
374
|
+
ctxlint is the reference implementation of two open specifications for linting AI agent interfaces. These specs are tool-agnostic — any linter, IDE extension, or CI system can implement them.
|
|
375
|
+
|
|
376
|
+
| Spec | What it covers |
|
|
377
|
+
|------|---------------|
|
|
378
|
+
| **[AI Context File Linting Spec](./CONTEXT_LINT_SPEC.md)** | 19 rules for validating context files (CLAUDE.md, .cursorrules, AGENTS.md, etc.) across 17 clients. Covers file formats, frontmatter schemas, path/command validation, staleness, token budgets, redundancy, and contradictions. |
|
|
379
|
+
| **[MCP Config Linting Spec](./MCP_CONFIG_LINT_SPEC.md)** | 23 rules for validating MCP server configs (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, etc.) across 8 clients. Covers schema validation, hardcoded secrets, env var syntax, deprecated transports, and cross-file consistency. |
|
|
380
|
+
|
|
381
|
+
Both specs include machine-readable rule catalogs for programmatic consumption:
|
|
382
|
+
- [`context-lint-rules.json`](./context-lint-rules.json) — context file rules and 16 supported format definitions
|
|
383
|
+
- [`mcp-config-lint-rules.json`](./mcp-config-lint-rules.json) — MCP config rules and 8 client definitions
|
|
384
|
+
|
|
244
385
|
## Also By Yaw Labs
|
|
245
386
|
|
|
246
387
|
- [Yaw](https://yaw.sh) — The AI-native terminal
|