---
title: "Directory readiness"
description: "m3 lint --directory checks an MCP server's metadata against the published listing rules of the Claude connector directory, the OpenAI plugin directory and the Muse connector directory, and links each finding to the policy text it comes from."
---

> M3 v0.3.2 · commit 59b4f085398f5e99e73d4dd0b1fb257662a56be6.


# Directory readiness

`m3 lint --directory` checks an MCP server's metadata against the published
listing rules of three directories:

- `claude`: the Claude connector directory;
- `openai`: the OpenAI plugin directory, which ChatGPT and Codex share;
- `muse`: the Muse connector directory (Meta).

```sh
m3 lint https://example.com/mcp --directory all
m3 lint --directory claude --directory openai -- python server.py
m3 lint --directory claude --harness claude_code -- python server.py
```

`--directory` is repeatable and `all` picks the three. With directories alone,
only directory checks run; add `--harness` to run the Claude Code and Codex
checks as well. Without `--directory`, `m3 lint` behaves exactly as before.
Like every lint check, directory checks only read what the server lists: they
never call a tool, never contact a model and never print header values.
With directories alone, a `tools/list` that fails, or that has entries which
are not tools with a string name, exits with status 2: nothing could be
checked. Run without `--directory` to see what is wrong with the listing.

**Passing does not mean a directory will approve the server.** Reviewers also
test behaviour, authentication, privacy policies, documentation and use
cases, none of which metadata shows. These checks catch the listing rules that
the metadata can show, before a reviewer does. For the checks that need
judgment, install the
[preparing-directory-submissions agent skill](https://m3.sineframe.com/docs/guides/agents/skill.md#prepare-a-directory-submission):
your coding agent runs these lint checks, then reads the code, docs and
listing, and writes one readiness report.

## Reading a finding

```
ERROR   directory-claude-safety-hints  tool 'create_issue' annotations
  detail: readOnlyHint absent, destructiveHint absent
  claude [error]: missing the applicable safety hint. https://claude.com/docs/connectors/building/review-criteria#provide-tool-annotations (policy checked 2026-10-18)
  fix: Set "readOnlyHint": true if this tool only reads; otherwise set "destructiveHint" (true if it modifies or deletes data). Claude's MCP page asks for both hints on every tool.
```

Each finding names the rule and the field, the observed fact, one line per
chosen directory with its severity and the policy section it comes from, and
a fix. The JSON report carries the same per directory under `directories`,
including the verbatim policy `quote` and `policy_date`. The last line of the
human report counts findings per directory. If `tools/list` failed or stopped
early, that line starts with `directory readiness: incomplete` and covers only
the tools that were read.

- **error**: the policy states a requirement or a rejection reason, and every
  tool matching the condition breaks it, whatever the tool does. Errors fail
  the run under the default `--fail-on error`.
- **warning**: the policy rule is real, but the condition only suggests a
  breach (for example, a method input that lists both GET and POST, or
  invisible characters that may hide text). The summary says "may", and the
  fix says what to check first. Warnings fail the run only with
  `--fail-on warning`.

## Policy versions

The rules were checked against each directory's published pages on
**2026-10-18**, and every report shows that date. Directories change their
rules; when they do, the quotes and the date are updated together.
`python3 benchmarks/lint-oracle/oracle.py --policies` checks every quote
against its live section.

## Rules

Each row is one directory effect: the rule, the directory, the severity, the
finding variants it applies to, the verbatim policy text it rests on, and why
the condition breaks it.

<!-- directory-rules:begin (generated by cli/tests/test_directory_rules.py) -->
| Rule | Directory | Severity | Applies to | Policy | Why |
|---|---|---|---|---|---|
| `tool-description-not-string` | claude | warning | all findings | [“Instructional Software must define each tool or capability through narrow, unambiguous natural language that specifies what it does and when it should be invoked.”](https://support.claude.com/en/articles/13145358-anthropic-software-directory-policy#h_7dac153147) | 2A asks for a natural-language definition of each tool but does not name the description field; a tool with a clear title may still satisfy it, so this is not proven. |
| `tool-description-not-string` | openai | error | all findings | [“Each tool must have a nonempty description that explicitly and accurately explains its purpose and behavior, when to use it, and any relevant limitations or side effects.”](https://developers.openai.com/plugins/plugin-guidelines#descriptions-that-match-behavior) | The sentence names the field and the condition: a missing, blank or non-text description breaks it for every tool. |
| `tool-name-collision` | openai | error | duplicate | [“Tool names must be unique within your MCP server.”](https://developers.openai.com/plugins/plugin-guidelines#clear-and-accurate-tool-names) | Two tools with the same name break the sentence directly. |
| `description-missing` | claude | warning | all findings | [“Instructional Software must define each tool or capability through narrow, unambiguous natural language that specifies what it does and when it should be invoked.”](https://support.claude.com/en/articles/13145358-anthropic-software-directory-policy#h_7dac153147) | 2A asks for a natural-language definition of each tool but does not name the description field; a tool with a clear title may still satisfy it, so this is not proven. |
| `description-missing` | openai | error | all findings | [“Each tool must have a nonempty description that explicitly and accurately explains its purpose and behavior, when to use it, and any relevant limitations or side effects.”](https://developers.openai.com/plugins/plugin-guidelines#descriptions-that-match-behavior) | The sentence names the field and the condition: a missing, blank or non-text description breaks it for every tool. |
| `directory-tool-title-missing` | claude | error | all findings | [“Every tool must include a title and the applicable hint: readOnlyHint: true for read-only tools, and destructiveHint: true for tools that modify or delete data.”](https://claude.com/docs/connectors/building/review-criteria#provide-tool-annotations) | The sentence requires a title on every tool. Claude does not say which field, so either title or annotations.title satisfies the check; a blank title counts as none. |
| `directory-claude-safety-hints` | claude | error | no-applicable | [“Every tool must include a title and the applicable hint: readOnlyHint: true for read-only tools, and destructiveHint: true for tools that modify or delete data.”](https://claude.com/docs/connectors/building/review-criteria#provide-tool-annotations) | Claude defines readOnlyHint as 'the tool only reads data' and destructiveHint as 'the tool can modify or delete data', so every tool falls under one of them. This tool is not marked read-only and declares no destructiveHint, so it lacks the applicable hint whichever it is; it also breaks the MCP page's 'must declare both'. |
| `directory-claude-safety-hints` | claude | warning | one-declared | [“All MCP tools must declare both of these annotations: … readOnlyHint: the tool only reads data … destructiveHint: the tool can modify or delete data”](https://claude.com/docs/connectors/building/mcp#tool-hints) | Claude's MCP page asks for both hints on every tool, while the review criteria accept the applicable one, which this tool declares (readOnlyHint: true or destructiveHint: true); listed servers ship read-only tools without destructiveHint. |
| `directory-claude-safety-hints` | claude | warning | read-only-undeclared | [“Every tool must include a title and the applicable hint: readOnlyHint: true for read-only tools, and destructiveHint: true for tools that modify or delete data.”](https://claude.com/docs/connectors/building/review-criteria#provide-tool-annotations) | destructiveHint is false and readOnlyHint is not declared. If the tool only reads, Claude's applicable hint (readOnlyHint: true) is missing; if it writes, the declared destructiveHint applies. |
| `directory-claude-safety-hints` | claude | warning | write-not-destructive | [“Every tool must include a title and the applicable hint: readOnlyHint: true for read-only tools, and destructiveHint: true for tools that modify or delete data.”](https://claude.com/docs/connectors/building/review-criteria#provide-tool-annotations) | readOnlyHint false says the tool modifies data, and Claude asks for destructiveHint: true on tools that modify data. Whether an additive write such as creating a record counts is not stated, and readOnlyHint false may itself be wrong for a tool that only returns a link. |
| `directory-hints-not-explicit` | openai | error | all findings | [“Set readOnlyHint, destructiveHint, and openWorldHint to explicit boolean values (true or false) in each tool’s annotations object.”](https://developers.openai.com/plugins/plugin-guidelines#correct-annotation) | The sentence requires all three hints as booleans on every tool; a missing or non-boolean hint breaks it directly. |
| `directory-hints-contradict` | openai | error | all findings | [“readOnlyHint: Use true for retrieval, computation, or previews that do not modify the environment. Use false for external-state changes … destructiveHint: Use false for read-only tools.”](https://developers.openai.com/plugins/plugin-guidelines#correct-annotation) | If the tool is read-only, the destructiveHint line requires false; if it changes anything, the readOnlyHint line requires false. Either way one of the two hints breaks a rule. |
| `directory-read-write-tool` | claude | warning | http-methods | [“A single tool that accepts both safe HTTP methods, such as GET, HEAD, and OPTIONS, and unsafe methods, such as POST, PUT, PATCH, and DELETE, is rejected. Don't ship a catch-all api_request tool with a method parameter.”](https://claude.com/docs/connectors/building/review-criteria#separate-read-and-write-tools) | The method input lists both safe and unsafe methods, which Claude rejects. The schema can't show whether the server refuses some of them at run time. |
| `directory-read-write-tool` | openai | warning | http-methods | [“Expose each model-callable operation as a separate tool with a clear description, input schema, and annotations. Do not use discovery, operation selection, or schema fetching with a generic executor to enable operations not individually exposed for review.”](https://developers.openai.com/plugins/plugin-guidelines#tool-independence-and-exposure) | OpenAI asks for each operation as a separate tool and forbids operation selection through a generic executor; the allowed values suggest that, which the schema alone cannot confirm. |
| `directory-hidden-content` | claude | warning | all findings | [“Instructional Software must not contain hidden, obfuscated, or encoded instructions. All behavioral guidance must be human-readable and clearly presented.”](https://support.claude.com/en/articles/13145358-anthropic-software-directory-policy#h_7dac153147) | Invisible characters, encoded text, comments or long blank runs are present; whether they carry instructions is a judgment the check cannot make. |
| `directory-hidden-content` | openai | warning | all findings | [“Act only within the user-authorized task. Plugin and tool metadata must not seek secrets or protected context, direct unauthorized data transfers or actions, override platform instructions or safeguards, conceal behavior, or impersonate another party. Encoded or obfuscated instructions do not exempt a plugin from these requirements.”](https://developers.openai.com/plugins/plugin-guidelines#respect-user-intent) | Invisible characters, encoded text, comments or long blank runs are present; encoding alone is no breach, and whether they conceal behaviour is a judgment the check cannot make. |
| `directory-tool-name-too-long` | claude | error | ascii | [“Tool names must be 64 characters or fewer.”](https://claude.com/docs/connectors/building/review-criteria#keep-tool-names-short) | The name is ASCII and over 64 characters, so every way of counting characters gives more than 64. |
| `directory-tool-name-too-long` | claude | warning | non-ascii | [“Tool names must be 64 characters or fewer.”](https://claude.com/docs/connectors/building/review-criteria#keep-tool-names-short) | The name has more than 64 code points; with combining characters it may have 64 or fewer visible characters. |
<!-- directory-rules:end -->
## Not checked

- **Anything that depends on what text means.** This covers steering
  language in descriptions or server instructions (preferring this tool,
  avoiding other tools or web search, asking for calls on every turn,
  overriding instructions), unsupported use cases (moving money, placing
  trades, generating media with AI), tool names that contradict their hints,
  free-form API tools that don't name their API, and inputs that ask for
  credentials, payment data, government IDs, precise location or conversation
  history. Patterns can't decide these correctly; they need judgment.
- **Muse.** Muse classifies tools from your documentation rather than MCP
  annotations, and its listing rules are about meaning, so no rule checks
  Muse from metadata yet. `--directory muse` reports it as not checked, never
  as passing.
- **Listing names**, such as OpenAI's rule against appending "MCP" or
  "Plugin" to a product name: the listing name isn't in the server's
  metadata.
- **Empty tool names:** no directory addresses them (the Claude Code and
  Codex checks still do).
- **Anything that needs calling a tool:** response size, error messages,
  authentication, and whether descriptions match behaviour.
