Discovery answered
no scoreserver/discover must return supportedVersions, capabilities and server identity. Moonpeak calls it first, the way the spec recommends.
Fix Implement server/discover; it is mandatory in 2026-07-28.
Scoring
Moonpeak grades a server twice: once on how well it speaks MCP, once on how safe its tools are to hand to a model. Here is every rule, what it looks for and how to win the points back.
Both scores start at 100. A failed check removes 20 points, a warning removes 6, an informational result costs nothing, and neither score goes below 0. Protocol checks adapt to the server's era: a 2026-07-28 server is tested on discovery, negotiation, headers and caching; an older one on its handshake. If the server cannot be reached at all, there is no score, only the reason.
Grouped by protocol era. fail −20 warn −6 info 0
server/discover must return supportedVersions, capabilities and server identity. Moonpeak calls it first, the way the spec recommends.
Fix Implement server/discover; it is mandatory in 2026-07-28.
supportedVersions should include 2026-07-28.
Fix Upgrade to an SDK release that speaks the current revision.
Moonpeak sends a request for version 1900-01-01. The server must answer error -32022 with a data.supported list.
Fix Reject unknown versions with UnsupportedProtocolVersionError, HTTP 400.
Moonpeak sends tools/list with an Mcp-Method: prompts/list header. The server must refuse it with -32020: gateways route on headers, so a mismatch is a security hole.
Fix Compare MCP-Protocol-Version, Mcp-Method and Mcp-Name with the body and answer 400 HeaderMismatch.
Every result must say "resultType": "complete" or "input_required".
Fix Add the field to every result object.
server/discover, lists and resources/read must include ttlMs (integer ≥ 0) and cacheScope (public or private).
Fix Use "ttlMs": 3600000, "cacheScope": "public" for static lists.
Two consecutive tools/list calls should return the same order, so clients and model prompt caches stay warm.
Fix Sort tools by name, or keep registration order stable.
initialize must return a result. Moonpeak falls back to it when server/discover is not understood.
Fix Answer POST requests on the MCP route with a JSON or SSE response to initialize.
Any handshake-based revision (2025-11-25 and earlier) works, with a warning that 2026-07-28 exists.
Fix Add server/discover and per-request _meta handling; keep initialize for old clients if you need them.
notifications/initialized should get 202 Accepted. Another 2xx is a warning; an error status is a failure.
Fix Return 202 with an empty body for any JSON-RPC message without an id.
ping must return an empty result in legacy revisions.
Fix Handle ping and reply {}.
A server name and version, from serverInfo or the io.modelcontextprotocol/serverInfo metadata.
Fix Set them when you create the server, for example { name: "acme", version: "1.4.0" }.
A server that declares tools must answer tools/list. A failure is −20; no capability or an empty list is a warning.
Fix Declare capabilities.tools only when you expose tools, and paginate with nextCursor.
Names must match ^[A-Za-z0-9_.-]{1,128}$ and be unique across all pages.
Fix Use snake_case verbs like search_issues; no spaces or slashes.
Each tool needs a description of at least 20 characters. The model reads it to decide when to call the tool.
Fix Say what the tool does, when to use it and what it returns.
inputSchema must exist and have "type": "object", even for tools without arguments.
Fix Use { "type": "object", "properties": {} } as the minimum.
Each property in every input schema should carry a description.
Fix Describe format and units: "UTC date, YYYY-MM-DD" beats "date".
Each tool should set readOnlyHint or destructiveHint, so clients know what may need confirmation.
Fix Add "annotations": { "readOnlyHint": true } to tools that only read.
Every _meta.ui.resourceUri (or openai/outputTemplate) must be readable with resources/read as HTML. Servers without UI get an informational note.
Fix Serve the resource with mimeType: "text/html;profile=mcp-app".
Median round trip of every normal call. Under 800 ms passes, under 2 s warns, slower fails.
Fix Keep list calls static and cached; avoid cold starts on the MCP route.
Everything a model reads from the server is scanned: tool and argument descriptions, prompt and resource descriptions, and the server instructions.
Tool poisoning patterns: "ignore previous instructions", "do not tell the user", <IMPORTANT> tags, references to the system prompt or conversation, local secret files, requests to send data to an endpoint or to pass credentials along, forced tool ordering.
Fix Describe what the tool does. Never address the model with orders about other behaviour.
Zero-width, bidirectional-override and Unicode tag characters in names or descriptions.
Fix Strip them; they have no legitimate use in tool metadata.
Descriptions that mention tools the server does not expose, "other servers" or "all other tools".
Fix Keep each description about its own tool.
URLs and IP addresses inside model-facing text, a common path for exfiltration.
Fix Keep links in your documentation, not in descriptions.
readOnlyHint: true on a tool whose name says it deletes, sends, writes or transfers.
Fix Set hints that match what the tool really does.
Tools named like delete_*, send_* or update_* without destructiveHint.
Fix Add "destructiveHint": true so clients ask before running them.
Shell, eval or command-runner tools exposed to the model.
Fix Narrow them to specific operations, or keep them behind authentication.
Any model-facing text longer than 1,500 characters.
Fix Keep descriptions short; move long guides to documentation.
Every probe fingerprints each tool. The site warns when definitions changed since your last probe; the score API fails when they differ from a pinned fingerprint.
Fix Review changes, then pin the new fingerprint in your CI.
Tool linter
Paste the result of tools/list, or just its tools array. The tool rules and the security scan run in your browser with the same engine as the probe; nothing leaves the page.
| Tool | Issues |
|---|