aitidalwave/ mcp-probe

A linter for MCP servers

Lint your MCP server
before an agent finds the bug.

One command. Connects to your server, runs the handshake, walks every tool, and reports exactly what's malformed. Non-zero exit, so it drops straight into CI.

$ npx mcp-probe ./server
mcp-probe — zsh
$
mcp-probe 0.1.0
connecting stdio node server.js
initialize protocolVersion 2025-06-18
capabilities tools, resources
tools/list 7 tools
search_products
get_product
create_order inputSchema.required lists "customer_id" — not in properties
update_inventory inputSchema missing "type"
! cancel_order description is 4 words — agents pick tools by description
get_order_status
list_orders $ref "#/defs/Filter" does not resolve
resources/list 0 resources
error probe tools/call unknown_tool
returned raw exception, not a JSON-RPC error object (-32601 expected)
4 errors 1 warning 7 tools checked in 0.8s
exit 1

mcp-probe is a command-line linter for Model Context Protocol servers. It runs your server, speaks the protocol to it, and reports where the server's answers don't match the spec.

Same idea as
ESLint for JavaScript. go vet for Go. A static check you run before shipping — except MCP servers can't be read statically, so it starts the server and asks.
What it looks at
The initialize handshake, the declared capabilities, and every tool the server exposes — its name, its description, and its inputSchema. Then it deliberately sends a bad request to see how the server fails.
What you get
A list of findings, each tied to one tool and one rule. Exit code 0 if clean, 1 if anything is broken. Nothing is uploaded; nothing phones home.

What it checks

Every check maps to something that actually breaks when an agent calls your server. Errors fail the run. Warnings don't — but they're the ones that cause wrong tool selection.

CheckWhat breaks if you skip it
initialize handshakeClient rejects the connection outright. Protocol version must be one the client understands; capabilities must be declared for anything you serve.
inputSchema is valid JSON SchemaEvery entry in required must exist in properties. Every property needs a type. Every $ref must resolve. Clients silently drop tools with broken schemas.
JSON-RPC 2.0 error shapeAn unknown tool or bad params has to come back as a proper error object with a code — not a stack trace. Agents can recover from -32602. They can't recover from a string.
tool names are unique & well-formedDuplicate or empty names collide in the client's tool table. Some clients reject names outside [a-zA-Z0-9_-].
descriptions carry enough to choose onThe description is the prompt. Too short, and the model can't tell cancel_order from delete_order. Flags very short, empty, and placeholder text.
free-text params are constrainedA string with no enum, pattern, or maxLength is an open door. Warned, not failed — sometimes it's intentional.

Drop it in CI

Exit 0 on clean, exit 1 on any error. Runs against stdio or streamable HTTP. No account, no service, no telemetry — it's a CLI.

# .github/workflows/mcp.yml
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx mcp-probe ./server

What it isn't

Narrow on purpose.

  • Not MCP Inspector. Inspector is an interactive UI for poking at a server by hand. This is a non-interactive check with an exit code. Use both.
  • Not a test of your tool logic. It checks that create_order is callable correctly — not that it creates an order.
  • Not a security scanner. It'll flag an unconstrained string. It won't tell you the server exfiltrates data. That's a different tool.

One core, four tools

The connect-and-walk library under mcp-probe is shared. The next three are thin layers on it, and they ship in this order.

  1. mcp-probeProtocol + schema conformance. Exit code for CI.
  2. mcp-readyRegistry readiness — validates server.json, emits submission payloads.
  3. mcp-describeLints tool descriptions as the prompts they are — ambiguity, overlap, missing failure cases.
  4. mcp-diffSnapshots the tool surface and diffs across versions. Catches quietly widened capabilities.