ZyVOP Logo
Content That Connects
SeriesAI NewsCategoriesWrite for Us
ZyVOP Logo
Content That Connects

Empowering developers and creators with cutting-edge insights, comprehensive tutorials, and innovative solutions for the digital future.

Content

  • Tags
  • Badges
  • Write Article
  • Newsletter

Company

  • About Us
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

© 2026 ZyVOP. Crafted with care for the developer community.

Made with ❤️ by the ZyVOP team
All systems operational
HomeTutorialWhy Cosine Similarity Fails to Catch Confusable MCP Tools
Tutorial

Why Cosine Similarity Fails to Catch Confusable MCP Tools

Author
AuthorSenior Developer
July 31, 2026
3 min read
6 views
#MCP#AI agents#LLM Tools#open-source#Python#Agent Safety#Schema Design#developer tools
👍3

An MCP agent decides what to do based entirely on text it's handed at runtime — a tool's name, its description, and its input schema. When a server exposes multiple tools with overlapping schemas or vague boundaries, an agent can easily invoke the wrong one, or take a destructive action it wasn't meant to.

I wanted a way to catch this before it happens: flag pairs of tools that are similar enough to confuse an agent, before that server ever gets used. The obvious first approach was cosine similarity on the tool descriptions. It completely failed.

The experiment

I ran TF-IDF + cosine similarity against the 14 tools exposed by the official MCP filesystem server — 91 possible pairs. At the standard 0.85 similarity threshold, it flagged zero pairs.

Lowering the threshold didn't help — it made things worse. Genuinely confusable pairs like read_file / read_text_file and completely unrelated pairs like read_file / write_file ended up scoring in the same range. There was no threshold, anywhere, that cleanly separated "these will confuse an agent" from "these obviously won't."

Why it breaks down

Two things were going on:

  1. Shared domain vocabulary swamps the signal. Tools on the same server all talk about path, file, directory — regardless of whether they're actually confusable. That overlap dominates the similarity score.

  2. Opposing verbs barely move the needle. read_file and write_file share the vast majority of their tokens. The one word that actually matters — read vs. write — is a tiny fraction of the text, so it has almost no effect on a similarity score built from shared nouns.

In short: cosine similarity on tool descriptions measures topical overlap, not functional confusability. Those are different things, and the difference is exactly what matters here.

What actually worked

Instead of scoring text first, I flipped the question: can one tool's arguments satisfy the other tool's schema? If they can't, an agent can never actually confuse the two calls, no matter how similar the descriptions read. So that pair gets thrown out before any text comparison happens at all.

This structural gate — checking schema substitutability first — eliminated 63 of the 91 pairs immediately, before any similarity scoring touched them.

For the remaining 28 pairs, I scored on:

  • name affinity

  • non-domain token overlap (ignoring the shared path/file/directory noise)

  • a hard veto on opposing verbs (read/write, create/delete)

This surfaced exactly 4 genuinely confusable pairs — the read_file / read_text_file / read_media_file cluster, and list_directory / list_directory_with_sizes — cleanly separated from everything else by a real 0.33–0.50 gap. That's the separation cosine similarity alone could never produce.

A note on scope

This is about accidental confusability between two legitimate, honestly-described tools — not malicious or poisoned tool descriptions (e.g. prompt injection hidden in metadata). That's a different, already-covered problem. This is about well-intentioned tools that are just similar enough to trip an agent up.

Try it

I packaged this as mcplock, an open-source lint check for MCP servers:

bash

pip install mcplock
mcplock lint "npx -y @modelcontextprotocol/server-filesystem ./data"

mcplock also does drift detection — it hashes tool definitions into a baseline so any runtime change to a tool's description, schema, or behavioral annotations becomes a reviewable diff, the same way a lockfile works for dependencies.

  • Repo: https://github.com/yash161004/mcplock

  • PyPI: https://pypi.org/project/mcplock/

If you're building or shipping MCP servers, I'd genuinely value you running it against yours and telling me what breaks.


The core takeaway: functional confusability is a structural property of tool schemas, not a text-similarity score — checking substitutability first is what actually separates risky tool pairs from safe ones.

What's your MCP server's tool count? Curious how common this problem actually is at scale.

Author

Author

Passionate developer sharing knowledge about modern web technologies and best practices.

Comments (0)

Login to post a comment.

Related Posts

The Alpine Mirage: How Upgrading Python Broke My Build and Led to a Truer Security Posture

A deep dive into how upgrading a Python Docker image to Alpine 3.15 caused cascading build failures across C/Rust dependencies—and why switching to Debian-slim with non-root privileges resulted in a faster, truly secure container architecture.

Read article

Why I Validate Angular Compatibility Using the Published npm Package (Not the Source Code)

Most Angular libraries claim compatibility across multiple Angular versions—but how many actually verify it? Here's why I stopped testing my source code and started validating the packaged npm artifact that users really install.

Read article

AI at Scale: Cost Cuts, New Tooling, and Growing Governance Rules

OpenAI’s price‑cut on GPT‑5.6, open‑source model distillation, emerging agent tooling, and a wave of contribution bans show AI moving from hype to production‑grade workloads, reshaping who wins and who must adapt.

Read article

I Stopped Copy-Pasting the Same Angular ApiService. Here’s What I Built Instead

Four projects in, I was copying the same api.service.ts instead of writing it. The design decisions behind extracting it into @ismailza/ngx-api-client.

Read article

Building a Zero-Trust Internal API on AWS

A DynamoDB table, a read-only Lambda, an API Gateway locked to IAM auth, and an EC2 client with no SSH key: a full zero-trust internal API on AWS, with working Python and Node.js signing code and a private-API hardening step.

Read article