{
  "markdown": "# mcp-lint\n\n[![npm version](https://img.shields.io/npm/v/mcp-lint?color=crimson)](https://www.npmjs.com/package/mcp-lint)\n[![npm downloads](https://img.shields.io/npm/dm/mcp-lint)](https://www.npmjs.com/package/mcp-lint)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Node.js ≥20](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)\n[![MCP server ready](https://img.shields.io/badge/MCP-server_ready-6f42c1)](#mcp-server)\n[![robert19001-cmyk/mcp-lint MCP server](https://glama.ai/mcp/servers/robert19001-cmyk/mcp-lint/badges/score.svg)](https://glama.ai/mcp/servers/robert19001-cmyk/mcp-lint)\n\n**Lint MCP server tool schemas for cross-client compatibility.**\n\nMCP servers expose tool schemas consumed by **Claude**, **Cursor**, **Gemini**, **VS Code Copilot**, **Windsurf**, **Cline**, **OpenAI Agents SDK**, and **Continue.dev**. Each client has different JSON Schema support and quirks — a schema that works in Claude may silently break in Cursor or OpenAI. `mcp-lint` catches these issues before your users do.\n\n```\n$ npx mcp-lint check tools.json\n\nsearch-tool\n  ✖ Schema node at \"inputSchema.properties.query\" has `required: false` which is not valid JSON Schema.\n    inputSchema.properties.query  [no-required-false]\n  ⚠ Parameter \"limit\" in tool \"search-tool\" is missing a description.\n    inputSchema.properties.limit  [description-exists]\n\n1 error, 1 warning\n```\n\n---\n\n## Installation\n\n```bash\n# One-time use (no install)\nnpx mcp-lint check tools.json\n\n# Global\nnpm install -g mcp-lint\n\n# Per-project dev dependency\nnpm install --save-dev mcp-lint\n```\n\n---\n\n## Quick Start\n\n```bash\n# Check a JSON or YAML file\nmcp-lint check tools.json\n\n# See compatibility across all clients at a glance\nmcp-lint compat tools.json\n\n# Learn what a rule does\nmcp-lint explain openai/no-additional-properties\n\n# Auto-fix what can be fixed\nmcp-lint fix tools.json --in-place\n\n# Generate a config file\nmcp-lint init\n\n# Run as an MCP server\nmcp-lint-server\n```\n\n---\n\n## `mcp-lint compat` — Compatibility Matrix\n\nSee exactly which tools work with which clients:\n\n```\n$ mcp-lint compat tools.json\n\n                    claude    cursor    gemini    vscode    windsurf  cline     openai    continue\nsearch-tool         ✓         ✗         ✓         ✓         ✓         ✓         ✓         ✓\nfile-reader         ✓         ✓         ✗         ✓         ✓         ✓         ✓         ✗\ndatabase-query      ✓         ✓         ✓         ✓         ✓         ✓         ✗         ✓\n\n2 tools have compatibility issues. Run `mcp-lint check` for details.\n```\n\n```bash\nmcp-lint compat tools.json --format markdown         # for PRs and GitHub Step Summary\nmcp-lint compat tools.json --format json             # machine-readable\nmcp-lint compat tools.json --client openai           # single client column\nmcp-lint compat --server stdio -- node server.js\n```\n\n---\n\n## `mcp-lint explain` — Rule Documentation\n\n```\n$ mcp-lint explain openai/no-additional-properties\n\nRule: openai/no-additional-properties\nSeverity:  error\nClients:   openai\nFixable:   yes\n\nDescription\nOpenAI Agents SDK strict mode requires `additionalProperties: false` on the root inputSchema.\n\nWhy\nOpenAI's strict function calling mode rejects schemas that don't explicitly set\n`additionalProperties: false` at the root level.\n\nBad example\n{\n  \"type\": \"object\",\n  \"properties\": { \"q\": { \"type\": \"string\" } }\n}\n\nGood example\n{\n  \"type\": \"object\",\n  \"properties\": { \"q\": { \"type\": \"string\" } },\n  \"additionalProperties\": false\n}\n\nAuto-fix\nAdd `\"additionalProperties\": false` to the root inputSchema.\n```\n\n---\n\n## CLI Reference\n\n### `mcp-lint check [input]`\n\n```bash\nmcp-lint check tools.json\n\n# Output formats\nmcp-lint check tools.json --format terminal   # default, colored output\nmcp-lint check tools.json --format json       # machine-readable (CI/CD)\nmcp-lint check tools.json --format markdown   # for PRs and GitHub summaries\n\n# Filters\nmcp-lint check tools.json --clients claude,cursor,openai   # only these clients' rules\nmcp-lint check tools.json --severity error                 # errors only\nmcp-lint check tools.json --quiet                          # same as --severity error\nmcp-lint check tools.json --ignore \"debug-tool,internal-tool\"\n\n# Config\nmcp-lint check tools.json --config path/to/.mcplintrc.json\nmcp-lint check tools.json --no-color\n\n# Quality score (0–100 per tool with A-F grades)\nmcp-lint check tools.json --score\n\n# Watch mode (re-lints on file change)\nmcp-lint check tools.json --watch\n\n# Live servers\nmcp-lint check --server stdio -- node my-server.js\nmcp-lint check --server stdio -- python my_server.py arg1 arg2\nmcp-lint check --server sse --url http://localhost:3000/sse\n```\n\n**Exit codes:**\n\n| Code | Meaning |\n|------|---------|\n| `0` | No errors (warnings don't count) |\n| `1` | One or more errors found |\n| `2` | Invalid input or configuration error |\n\n---\n\n### `mcp-lint diff <before> <after>`\n\nCompare lint results between two versions of your schema — useful in CI to catch regressions.\n\n```bash\nmcp-lint diff tools-v1.json tools-v2.json\nmcp-lint diff tools-v1.json tools-v2.json --format markdown >> $GITHUB_STEP_SUMMARY\nmcp-lint diff tools-v1.json tools-v2.json --format json\n```\n\n**Exit codes:** `0` = no new errors introduced, `1` = new errors found, `2` = error\n\n---\n\n### `mcp-lint fix [input]`\n\nAuto-fixes issues that are safe to fix automatically (no semantic changes).\n\n```bash\nmcp-lint fix tools.json                            # fixed output to stdout\nmcp-lint fix tools.json --output fixed-tools.json  # write to file\nmcp-lint fix tools.json --in-place                 # overwrite original\nmcp-lint fix tools.json --dry-run                  # show what would be fixed\nmcp-lint fix tools.json --rules no-required-false,no-empty-enum  # specific rules only\n```\n\n---\n\n### `mcp-lint init`\n\nCreates a default `.mcplintrc.json` in the current directory.\n\n```bash\nmcp-lint init          # fails if file already exists\nmcp-lint init --force  # overwrite existing\n```\n\n---\n\n## Rules\n\n### Universal (all clients)\n\n| Rule | Severity | Fixable | Description |\n|------|----------|---------|-------------|\n| `no-required-false` | 🔴 error | ✅ | `required: false` on properties is not valid JSON Schema |\n| `no-content-encoding` | 🔴 error | ✅ | `contentEncoding` is not supported by MCP clients |\n| `no-empty-enum` | 🔴 error | ✅ | Empty `enum: []` makes a parameter impossible to satisfy |\n| `no-recursive-refs` | 🔴 error | ❌ | Circular `$ref` causes crashes in most clients |\n| `valid-json-schema-subset` | 🔴 error | ❌ | Unsupported keywords: `oneOf`, `anyOf`, `allOf`, `if/then/else`, `not`, `patternProperties`… |\n| `description-exists` | 🟡 warning | ❌ | Tools and parameters should have descriptions |\n| `max-depth` | 🟡 warning | ❌ | Schema nesting deeper than 5 levels hurts LLM comprehension |\n| `no-unsupported-formats` | 🟡 warning | ✅ | Obscure `format` values (`iri`, `json-pointer`, `regex`…) not widely supported |\n\n### Claude-specific\n\n| Rule | Severity | Fixable | Description |\n|------|----------|---------|-------------|\n| `claude/no-type-array` | 🟡 warning | ✅ | `\"type\": [\"string\", \"null\"]` array syntax may not work correctly in Claude |\n\n### Cursor-specific\n\n| Rule | Severity | Fixable | Description |\n|------|----------|---------|-------------|\n| `cursor/no-default-without-type` | 🔴 error | ✅ | Cursor requires explicit `type` when a `default` value is present |\n| `cursor/no-missing-title` | 🟡 warning | ❌ | Cursor displays tool and parameter titles in the tool picker |\n\n### Gemini-specific\n\n| Rule | Severity | Fixable | Description |\n|------|----------|---------|-------------|\n| `gemini/no-optional-without-default` | 🟡 warning | ❌ | Gemini handles optional params better when `default` is explicit |\n| `gemini/no-nested-objects` | 🟡 warning | ❌ | Gemini has limited support for object properties nested more than 2 levels |\n\n### VS Code Copilot-specific\n\n| Rule | Severity | Fixable | Description |\n|------|----------|---------|-------------|\n| `vscode/max-params` | 🟡 warning | ❌ | VS Code Copilot performance degrades with more than 15 parameters |\n\n### Windsurf-specific\n\n| Rule | Severity | Fixable | Description |\n|------|----------|---------|-------------|\n| `windsurf/no-union-types` | 🟡 warning | ❌ | `anyOf` with more than 2 variants not supported |\n\n### Cline-specific\n\n| Rule | Severity | Fixable | Description |\n|------|----------|---------|-------------|\n| `cline/description-max-length` | 🟡 warning | ✅ | Descriptions over 200 characters are truncated by Cline |\n\n### OpenAI Agents SDK-specific\n\n| Rule | Severity | Fixable | Description |\n|------|----------|---------|-------------|\n| `openai/no-additional-properties` | 🔴 error | ✅ | Strict mode requires `additionalProperties: false` on root inputSchema |\n| `openai/strict-types` | 🔴 error | ❌ | Only `string`, `number`, `boolean`, `object`, `array`, `null` allowed |\n\n### Continue.dev-specific\n\n| Rule | Severity | Fixable | Description |\n|------|----------|---------|-------------|\n| `continue/no-default-values` | 🟡 warning | ❌ | Continue.dev ignores `default` fields — document defaults in descriptions instead |\n\n---\n\n## Configuration\n\n`mcp-lint` auto-discovers `.mcplintrc.json` starting from the current directory up to your home folder. Override with `--config`.\n\n```json\n{\n  \"rules\": {\n    \"no-required-false\": \"error\",\n    \"description-exists\": \"warning\",\n    \"max-depth\": \"off\",\n    \"claude/no-type-array\": \"error\"\n  },\n  \"clients\": [\"claude\", \"cursor\", \"gemini\", \"vscode\", \"windsurf\", \"cline\", \"openai\", \"continue\"],\n  \"ignore\": [\"internal-debug-tool\"],\n  \"maxDepth\": 5\n}\n```\n\n### Presets\n\nUse `\"extends\"` to start from a built-in preset:\n\n```json\n{ \"extends\": \"recommended\" }\n```\n\n```json\n{\n  \"extends\": \"strict\",\n  \"rules\": {\n    \"description-exists\": \"warning\"\n  }\n}\n```\n\n| Preset | Description |\n|--------|-------------|\n| `recommended` | All 8 clients enabled, default severities |\n| `strict` | All rules set to `error`, `maxDepth: 3` |\n\nRule severities: `\"error\"` | `\"warning\"` | `\"info\"` | `\"off\"`\n\n---\n\n## CI/CD — GitHub Actions\n\n### Using the official Action\n\n```yaml\nname: MCP Schema Lint\non: [push, pull_request]\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: robert19001-cmyk/mcp-lint@v0.5.3\n        with:\n          input: tools.json\n          severity: warning\n          score: 'true'\n          fail_on: error\n```\n\n**Action inputs:**\n\n| Input | Default | Description |\n|-------|---------|-------------|\n| `input` | _(required)_ | Path to MCP tools JSON file |\n| `format` | `terminal` | `terminal\\|json\\|markdown` |\n| `severity` | `info` | Minimum severity to report |\n| `clients` | all | Comma-separated client filter |\n| `fail_on` | `error` | Fail CI when issues at this severity found |\n| `score` | `false` | Show quality score |\n\n### Manual workflow\n\n```yaml\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with: { node-version: 20 }\n\n      # Fail the build on errors\n      - run: npx mcp-lint check ./src/tools.json\n\n      # Post compatibility matrix to PR summary\n      - run: npx mcp-lint compat ./src/tools.json --format markdown >> $GITHUB_STEP_SUMMARY\n        if: always()\n\n      # Diff against previous version\n      - run: npx mcp-lint diff tools-before.json tools-after.json --format markdown >> $GITHUB_STEP_SUMMARY\n        if: always()\n```\n\n---\n\n## Plugin API\n\nShare and reuse rule sets via npm packages.\n\n### Using a plugin\n\n```bash\nnpm install mcp-lint-config-nextjs\n```\n\n```json\n{\n  \"plugins\": [\"mcp-lint-config-nextjs\"],\n  \"extends\": \"recommended\"\n}\n```\n\n### Writing a plugin\n\nA plugin is an npm package that exports a `rules` array of `Rule` objects:\n\n```typescript\n// mcp-lint-config-myserver/index.ts\nimport type { Rule } from 'mcp-lint';\n\nconst myRule: Rule = {\n  id: 'myserver/no-large-params',\n  severity: 'warning',\n  description: 'My server limits param count to 10',\n  clients: ['claude', 'cursor'],\n  check(tool) {\n    const count = Object.keys(tool.inputSchema.properties ?? {}).length;\n    if (count > 10) return [{\n      toolName: tool.name,\n      ruleId: 'myserver/no-large-params',\n      severity: 'warning',\n      message: `Too many params (${count})`,\n      path: 'inputSchema.properties',\n    }];\n    return [];\n  },\n};\n\nexport const rules = [myRule];\n```\n\n---\n\n## Programmatic API\n\n```typescript\nimport { LintEngine } from 'mcp-lint';\nimport { allRules } from 'mcp-lint/rules';\nimport { loadFile } from 'mcp-lint/loaders';\nimport { applyFixes } from 'mcp-lint/fixer';\n\n// Check\nconst tools = await loadFile('tools.json');\nconst engine = new LintEngine(allRules, {\n  clients: ['claude', 'cursor', 'openai'],\n  ignore: ['debug-tool'],\n});\nconst diagnostics = engine.lint(tools);\n\n// Fix\nconst fixed = applyFixes(tools, diagnostics);\n```\n\n---\n\n## `mcp-lint preflight` — Runtime Decision Firewall\n\n**Lint catches bad schemas. Preflight catches bad actions.**\n\nAt runtime, your agent is about to call a tool — delete a file, send a request, charge a card. Preflight sits between the agent and the action and answers:\n\n- Is it allowed?\n- How risky is it? (0.0–1.0 deterministic score)\n- Is it reversible?\n- Does it need human approval?\n- Is there a safer alternative?\n\n```bash\n$ mcp-lint preflight action.json --policy preflight.yml\n\nPreflight Decision\n────────────────────────────────────────\nDecision:      DENY\nRisk score:    1.00\nReversibility: irreversible\nPolicies:      block-destructive-prod\nReasons:       base_shell, destructive_pattern, sensitive_target, irreversible_operation\n```\n\n### Action format\n\n```json\n{\n  \"tool_type\": \"shell\",\n  \"tool_name\": \"bash\",\n  \"action\": \"rm -rf /prod/data\",\n  \"target\": \"/prod/data\",\n  \"context\": { \"environment\": \"prod\" }\n}\n```\n\n### Policy file (YAML)\n\n```yaml\nversion: 1\ndefaults:\n  approval_threshold: 0.70\n  block_threshold: 0.92\n\nrules:\n  - id: block-prod-delete\n    when:\n      tool_type: shell\n      action_matches: [\"rm -rf\"]\n      target_matches: [\"/prod\", \"/etc\"]\n    effect: deny\n\n  - id: rewrite-tmp-delete\n    when:\n      tool_type: shell\n      action_matches: [\"rm -rf ./tmp\"]\n    effect: allow_with_rewrite\n    rewrite:\n      tool_type: file_delete\n      action: move_to_trash\n      target: ./tmp\n\n  - id: approval-payments\n    when:\n      tool_type: payment\n    effect: require_approval\n```\n\nSample policies in `examples/policies/`: `default.yml`, `strict.yml`, `permissive-dev.yml`.\n\n### SDK — embed in your MCP server or agent runtime\n\n```typescript\nimport { preflight, loadPolicy } from 'mcp-lint/preflight';\n\nconst policy = await loadPolicy('./preflight.yml');\n\nconst decision = preflight(\n  {\n    tool_type: 'shell',\n    tool_name: 'bash',\n    action: 'rm -rf /tmp',\n    target: '/tmp',\n  },\n  policy,\n);\n\nif (decision.decision === 'deny') {\n  throw new Error(`Blocked: ${decision.reasons.join(', ')}`);\n}\nif (decision.decision === 'require_approval') {\n  await askUser(decision);\n}\nif (decision.decision === 'allow_with_rewrite') {\n  return executeSafer(decision.safe_alternative);\n}\n```\n\n### Exit codes\n\n| Code | Meaning |\n|------|---------|\n| `0`  | allow / allow_with_rewrite |\n| `1`  | require_approval |\n| `2`  | deny / error |\n\nUse them directly in shell wrappers or CI gates.\n\n---\n\n## MCP Server\n\n`mcp-lint` also ships as a stdio MCP server for Claude Desktop, Claude Code, Cursor, VS Code, and registry scanners such as Glama.\n\n```bash\nnpx -y mcp-lint@latest mcp-lint-server\n```\n\nClient config:\n\n```json\n{\n  \"mcpServers\": {\n    \"mcp-lint\": {\n      \"command\": \"npm\",\n      \"args\": [\"exec\", \"--yes\", \"--package\", \"mcp-lint@latest\", \"--\", \"mcp-lint-server\"]\n    }\n  }\n}\n```\n\nAvailable MCP tools:\n\n| Tool | Purpose |\n|------|---------|\n| `mcp_lint_list_rules` | List built-in rules, clients, severities, and fixability |\n| `mcp_lint_explain_rule` | Explain one rule with examples and auto-fix notes |\n| `mcp_lint_check_tools` | Lint MCP tool schemas passed as JSON |\n| `mcp_lint_fix_tools` | Return safely auto-fixed tool schemas without writing files |\n| `mcp_lint_preflight_action` | Score a proposed agent action and return allow/deny/approval/rewrite |\n\nAll server tools are read-only from the host perspective: they return diagnostics, fixed JSON, or policy decisions and never execute the action being evaluated.\n\n### Docker\n\n```bash\ndocker build -t mcp-lint-server .\ndocker run --rm -i mcp-lint-server\n```\n\nThe repository includes `glama.json` and a production Dockerfile so MCP directories can build, start, and introspect the server.\n\n---\n\n## License\n\nMIT © Robert\n",
  "bytes": 16582,
  "sha": "16fb5ac3930b913b5b620de59fb25cfd810126893e5265d13facd218598994eb",
  "repo_slug": "robert19001-cmyk/mcp-lint",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_robert19001_cmyk_mcp_lint_362c1b61/readme"
}