# Why would my MCP server get flagged for tool poisoning, and how do I check my tool descriptions for hidden text before I publish?

**Short answer:** Scanners and cautious clients flag tool definitions that contain text a reviewer can't see, because tool-poisoning attacks hide instructions that way. The usual culprits are zero-width characters, bidirectional controls, Unicode tag characters (U+E0000–U+E007F, which most interfaces render as nothing but models can read), HTML or markdown comments, CSS-hidden markup and long base64 runs, plus instruction-like phrasing aimed at the model, such as "ignore previous instructions" or "don't tell the user". Honest servers trip these by accident. Text pasted from documents, web pages or chat tools can carry zero-width spaces or a byte-order mark, generated docstrings keep HTML comments, and a description that says "always call this tool first" reads like steering. Before each release, dump tools/list and scan every string a model reads: names, titles, descriptions, every description nested in the schemas, enum values and defaults. Fail CI on invisible characters, hidden markup and encoded blobs, and rewrite directive wording into plain statements of what the tool does. scan_text checks for the classes Rightshold publishes in its open spec, free and with no key.

## Checklist
1. Dump the full tools/list your built server returns (every page) as JSON in CI. Scan the generated output, not just the source, because templating and build steps can add characters.
2. Scan every string a model reads, not only the top-level description: the tool name and title, each property description inside inputSchema and outputSchema at any depth, enum values and default strings.
3. Fail the build on invisible code points: zero-width characters (U+200B–U+200D, U+2060, U+FEFF), bidirectional controls (U+202A–U+202E, U+2066–U+2069), tag characters (U+E0000–U+E007F), control characters other than tab, line feed and carriage return, and private-use or unassigned code points.
4. Remove markup that hides content: HTML comments, markdown reference-style comments, and elements hidden with display:none, visibility:hidden, zero font size or text coloured like its background. Most clients show descriptions as plain text, so markup in them mostly serves to hide things.
5. Don't put base64 or other encoded blobs in descriptions. If a parameter needs an example, show a short readable one.
6. Rewrite directive phrasing as facts. Don't write "Always call this before any other tool"; write "Needs a project_id from list_projects." Never instruct the model about other tools or servers, the conversation or the user; text that tries to steer how other tools are used is what tool-shadowing checks look for.
7. Keep tool and parameter names to the characters the MCP spec recommends (A–Z, a–z, 0–9, underscore, hyphen and dot), which also rules out names that mix Latin letters with look-alike Cyrillic or Greek ones.
8. Re-run the scan against the live endpoint after deploy, then keep the clean output as your CI snapshot so a later change shows up as a diff.

## Why do MCP scanners flag hidden text in tool descriptions?
- Invariant Labs' April 2025 tool-poisoning disclosure showed instructions placed in a tool description that the model follows and the user never sees, because client interfaces show a summary or a short name rather than the full text the model reads. Anything that renders as nothing widens that gap, so scanners treat its presence as a finding whatever the intent.
- Unicode tag characters are the sharpest case. They map one-to-one onto ASCII, most interfaces don't display them, and models can still read them. Johann Rehberger's January 2024 write-up, which built on a discovery Riley Goodside posted, calls this ASCII smuggling.
- A finding is a fact about the text, not an accusation. Zero-width joiners are correct in emoji sequences and some scripts. If you really need one, expect to explain it to reviewers.

## What does a clean, scanner-safe MCP tool description look like?
- Plain sentences: what the tool does, what each parameter means, what it returns, any side effects (writes, sends, deletes, spends) and its limits. The MCP spec says there should always be a human able to deny tool invocations, and clear side-effect wording is what lets a client and a user make that call.
- No instructions about other tools, the conversation or the user, and no urgency aimed at the model. If order matters, state the dependency rather than commanding it.

## How do I scan my MCP tool descriptions for hidden characters before publishing?
- scan_text (MCP) or POST /v1/security/scan-text with {"text": "..."} takes up to 100,000 characters. It reports each deterministic finding (invisible characters, hidden markup, base64 blobs, which are decoded and re-scanned one level) with exact code-point positions and a visible excerpt such as ⟨U+200B ZERO WIDTH SPACE⟩, and returns a sanitized copy. Two heuristic classes, mixed-script identifiers and instruction-like language, are labelled heuristic and never certain.
- precall_check runs the same scan over every definition your live endpoint serves, so you can confirm the deployed text matches what you checked in CI. The classes and thresholds are published at /security/spec, free to build into your own linter.

## Ask Rightshold (free, no key)
Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code (one-click setup: https://www.rightshold.com/start?ref=seo), then ask: "Here is a tool description from my MCP server, exactly as tools/list returns it. Does it contain anything a reviewer can't see, or wording a scanner would read as instructions to the model?"

```json
{
  "tool": "scan_text",
  "arguments": {
    "text": "<the description, or the whole tools/list JSON>"
  }
}
```
- Free, no key. A clean result means none of the published classes were found, not that a description is safe.
- Heuristic findings (instruction-like language, mixed-script identifiers) are labelled heuristic: read them as prompts to reword, not verdicts.
- precall_check scans every definition your deployed server serves, and also reports whether they match the recorded fingerprint and whether another registered server offers the same tool name with a different definition.

_scan_text finds the classes in Rightshold's published spec. It doesn't judge what a tool does, review your code, or predict how a particular client or third-party scanner will score your server._

## Sources
- [Invariant Labs: MCP Security Notification: Tool Poisoning Attacks (April 2025)](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks)
- [Embrace The Red: ASCII Smuggler Tool: Crafting Invisible Text and Decoding Hidden Codes (Unicode tags, Jan 14, 2024)](https://embracethered.com/blog/posts/2024/hiding-and-finding-text-with-unicode-tags/)
- [MCP specification 2025-11-25: Tools (tool names, human in the loop, security considerations)](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)
- [Unicode Technical Standard #39: Unicode Security Mechanisms (mixed-script confusables)](https://www.unicode.org/reports/tr39/)
- [OWASP MCP Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/MCP_Security_Cheat_Sheet.html)
- [Rightshold agent-safety formats: invisible-text scan (sections 1–6)](https://www.rightshold.com/security/spec)

## Related
- [How to write MCP tool descriptions so agents pick the right tool](https://www.rightshold.com/answers/how-to-write-mcp-tool-descriptions-so-agents-pick-the-right-tool)
- [How to vet an MCP server before installing it](https://www.rightshold.com/answers/how-to-vet-an-mcp-server-before-installing-it)
- [Changing your MCP server's tool definitions without tripping rug-pull alarms](https://www.rightshold.com/answers/how-to-change-your-mcp-servers-tool-definitions-without-tripping-rug-pull-alarms)
- [How to prevent MCP tool shadowing between servers](https://www.rightshold.com/answers/how-to-prevent-mcp-tool-shadowing-between-servers)

_Not legal advice. Updated 2026-11-09. JSON: https://www.rightshold.com/answers/how-to-check-your-mcp-tool-descriptions-for-hidden-text-before-publishing.json_