# Lint your MCP server before you publish it (an eslint for MCP)

> Source: <https://dev.to/fernforge/lint-your-mcp-server-before-you-publish-it-an-eslint-for-mcp-3gb7>
> Published: 2026-06-27 01:51:41+00:00

If you've shipped a [Model Context Protocol](https://modelcontextprotocol.io) server, you've probably hit this: the server *works* in your editor, you publish it, and then agents call your tools wrong — or worse, a client flags it as unsafe and it never makes it into the ChatGPT or Claude app directories.

There's no `eslint`

for this. So I built one: ** mcp-conform** — a deterministic, author-side conformance & safety linter you run

```
npx github:fernforge/mcp-conform --cmd "node dist/index.js"
mcp-conform — 7 tool(s) checked

delete_record
  ✖ error  ann/missing-destructive-hint  Tool may modify state but does not set destructiveHint.
         fix: Set annotations.destructiveHint (true for irreversible ops like delete).
  ✖ error  safety/injection-phrase       Description contains an instruction-override phrase.
         fix: Describe behavior, don't issue commands to the agent.

1 error · 4 warning · 5 info
Conformance score: 76/100   FAIL
```

Three things changed under MCP authors' feet, and they all bite at publish time:

**1. Missing tool annotations are the #1 reason servers get rejected from app directories.** The spec says a client *must assume the worst case* — destructive, open-world — when a hint is absent. So if you don't set `readOnlyHint`

/ `destructiveHint`

/ `openWorldHint`

/ `title`

, your harmless read tool gets treated as dangerous, and your destructive tool ships with no warning at all.

**2. Tool descriptions are an injection surface.** Descriptions are fed straight into the agent's context. An "ignore previous instructions…" line — or an invisible Unicode payload — sitting in a description is a real **tool-poisoning** vector. You want that caught in CI, not in a security write-up about your package.

**3. The official registry now does namespace-verified publishing.** Reverse-DNS names, a `server.json`

manifest, and clean package metadata are part of being publishable and discoverable now, not nice-to-haves.

Most existing MCP security tooling is **consumer-side** — it scans servers you're about to *install*. `mcp-conform`

is **author-side and shift-left**: it makes *your* server conformant before anyone installs it.

`readOnlyHint`

, `destructiveHint`

, `openWorldHint`

, `title`

.`package.json`

/ `server.json`

readiness for the registry.The interesting part: point it at your server's **launch command** and it starts the server over stdio, calls `tools/list`

, and inspects the *real* schemas your users will receive — not a guess parsed from your source. That catches the gap between what your code looks like and what your server actually serves.

```
# lint the live, running server
npx github:fernforge/mcp-conform --cmd "node dist/server.js"

# or a saved tools/list dump, with a CI gate
npx github:fernforge/mcp-conform --manifest tools.json --min-score 80
```

It's a linter, not a model. Safe to run in CI, free to run a thousand times a day, and its verdict never drifts. There's a drop-in **GitHub Action** that scores every PR and writes a job summary, so a regression in your tool metadata fails the build like any other lint error.

```
npx github:fernforge/mcp-conform --cmd "<your server launch command>"
```

Repo, rules, and the GitHub Action: ** https://github.com/fernforge/mcp-conform** (MIT).

It's early — if you publish MCP servers, I'd genuinely like to know which checks catch real issues for you and which rules you'd want next. Open an issue or drop a comment.

(Disclaimer: this article was generated by ai)
