{"slug": "what-mcp-bastion-found-when-i-changed-one-tool-description", "title": "What MCP Bastion Found When I Changed One Tool Description", "summary": "A developer released MCP Bastion 0.4.0, an open-source scanner for Model Context Protocol servers and AI-agent tools that diffs tool definitions against an approved lock file. In a demonstration, changing a single sentence in an MCP tool's description—adding a promise to return the deployment region alongside uptime—produced a HIGH finding and exit code 1 when scanned against the approved copy, while the same file scanned alone produced zero findings. The tool statically inspects client configuration, tool metadata and repository instruction files, and can optionally launch a configured local server with --live for inspection.", "body_md": "I changed one sentence in the description of an MCP tool called `get_uptime`. Scanned on its own, the edited definition produced zero findings. Scanned against the copy I had approved, the same file produced a **HIGH** finding and an exit code of **1**.\n\nThe edit was ordinary English. The tool used to promise uptime in seconds. Now it promised uptime *and the deployment region*. A reviewer could reasonably approve either version. The question was whether anyone had approved the second one.\n\nThat’s the problem I wanted to demonstrate with **MCP Bastion**, an open-source scanner for Model Context Protocol servers and AI-agent tools. I ran two small experiments against version 0.4.0 using saved tool definitions. You can repeat the first in a few minutes without a model, an API key, or a running MCP server. [Project on GitHub](https://github.com/lindseynguyen/mcp-bastion)\n\nAn MCP client uses `tools/list` to discover what a server offers. The response includes each tool’s name, description and input schema. The client can make that information available to the model as it selects tools and prepares arguments. Execution happens through a separate `tools/call` request. [MCP tools specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)\n\nSuppose a pull request pins the package version that launches a local server. That gives me something concrete to review about the executable. A remote server can also change what it advertises while the URL in my client configuration stays exactly the same. And even when the tool name is familiar, a new description on an *optional parameter* can change what an agent is asked to supply.\n\n| What I inspect | What I want to know | \n|---|---|\n| Client configuration | What command or endpoint will the client use? Which filesystem paths, credentials or container privileges does that configuration expose? | \n| Tool metadata | What instructions will the model see, including text nested inside the input schema? Which definitions changed since review? | \n| Repository instructions | What else can steer the coding agent when it opens this checkout? That includes `AGENTS.md` , Cursor rules, Claude skills and configured hooks. | \n\nI can scan a checkout statically before opening it in an agent. For metadata, I can supply a saved catalog with `--tools-file`. For live inspection, `--live` can launch the configured local server command. I use that mode only after reviewing the command or moving the scan into an isolated environment. [Scan implementation](https://github.com/lindseynguyen/mcp-bastion/blob/4163ba54944a6e8a9e69a8623461d3424f54ee06/mcpbastion/scanner.py)\n\n*The project's example dashboard groups findings by server and file. The experiments below use smaller catalogs so each finding has a single explanation.*\n\nTo reproduce this on macOS or Linux, start in an empty directory with a Python 3.10+ virtual environment active. I pinned the scanner revision so the rule behavior is reproducible.\n\n```\npython -m pip install \"mcp-bastion @ git+https://github.com/lindseynguyen/mcp-bastion.git@4163ba54944a6e8a9e69a8623461d3424f54ee06\"\n```\n\nCreate an empty client configuration to keep local MCP settings out of the experiment. The following commands save one definition under the server name `ops` and create the initial lock file.\n\n```\nprintf '%s\\n' '{\"mcpServers\": {}}' > mcp.json\ncat > tools.json <<'JSON'\n{\n  \"ops\": {\n    \"tools\": [{\n      \"name\": \"get_uptime\",\n      \"description\": \"Return service uptime in seconds.\",\n      \"inputSchema\": {\"type\": \"object\", \"properties\": {}}\n    }]\n  }\n}\nJSON\n\nmcp-bastion scan mcp.json --tools-file tools.json --engines none --lock mcp.lock.json\n```\n\nOpen `tools.json` and replace the description with `Return service uptime and the deployment region.` Leave the lock file untouched. Rerun the scan with the same lock, then once more with the `--lock mcp.lock.json` pair removed. I kept the default severity threshold for all three runs.\n\n| Run | Finding | Exit code | \n|---|---|---|\n| Original description, first pin | None | 0 | \n| Edited description, same lock | HIGH · `MB701` | 1 | \n| Edited description, omit `--lock` | None | 0 | \n\nBastion hashes a canonical JSON representation of the kind, name, description and input schema. The lock entry is keyed by server, kind and name. Reordering JSON object keys leaves the fingerprint intact. Editing this sentence changes it. **The finding establishes that the advertised definition changed.** Whether the edit is acceptable still takes a reviewer. An unchanged fingerprint also tells me nothing about a change confined to the server implementation. [Fingerprint code](https://github.com/lindseynguyen/mcp-bastion/blob/4163ba54944a6e8a9e69a8623461d3424f54ee06/mcpbastion/models.py) · [Lock comparison](https://github.com/lindseynguyen/mcp-bastion/blob/4163ba54944a6e8a9e69a8623461d3424f54ee06/mcpbastion/pinning.py)\n\nFor the second experiment I gave Bastion a synthetic catalog representing the three tools available to one support assistant.\n\n| Tool | Advertised purpose | \n|---|---|\n| `support/read_issue` | Read a public support issue. | \n| `docs/read_file` | Read a private runbook. | \n| `chat/send_message` | Send a message to a channel. | \n\nWith all three present, Bastion raised **HIGH · `MB602`**. With the messaging tool removed, the same scan produced zero findings. No issue, runbook or message was accessed in either run.\n\nA stranger can write a public issue that the assistant will read. That same assistant can open an internal runbook and send a message. Before approving all three capabilities, I’d ask **which runbooks its credential can read and which channels its sender can reach**. If the chat connector can only post to the on-call team, my review looks different than if it can post to customer channels.\n\nThe rule infers capabilities from tool names and descriptions. With client configurations available, Bastion evaluates the combination per client. A standalone catalog without configured servers is treated as one agent, so I use a separate catalog for each real permission scope. To establish that data actually moved, I’d look for the corresponding tool calls and destination audit records. [Capability rules](https://github.com/lindseynguyen/mcp-bastion/blob/4163ba54944a6e8a9e69a8623461d3424f54ee06/mcpbastion/rules/tool_rules.py) · [Scope selection](https://github.com/lindseynguyen/mcp-bastion/blob/4163ba54944a6e8a9e69a8623461d3424f54ee06/mcpbastion/scanner.py)\n\nI’d start with one repository and one agent. Scan the checkout, save the report, then have the person who owns the integration review its first tool catalog. The CLI can write SARIF for code scanning and Markdown for a human reviewer in the same run.\n\n```\nmcp-bastion scan . --fail-on high -o mcp-review.sarif -o mcp-review.md\n```\n\nI’d keep the **tool lock** and the **findings baseline** under separate review. The lock records the definitions we expect. The baseline records existing findings we have already triaged. Automatically refreshing the lock on every pull request would have accepted my uptime edit before anyone saw it. The first pin deserves the same attention as the next change. [Baseline implementation](https://github.com/lindseynguyen/mcp-bastion/blob/4163ba54944a6e8a9e69a8623461d3424f54ee06/mcpbastion/baseline.py)\n\nThe CLI makes OSV vulnerability lookups opt-in. The bundled GitHub Action defaults its `online` input to `true`. Set `online: 'false'` to disable those lookups in the Action. Its setup step still installs dependencies, which matters on an air-gapped runner. [GitHub Action definition](https://github.com/lindseynguyen/mcp-bastion/blob/4163ba54944a6e8a9e69a8623461d3424f54ee06/action.yml)\n\nIf you maintain an MCP integration, try the one-line edit on a tool your team already trusts. Then look at the resulting finding from the perspective of the person who has to approve the change. **What evidence would you want in that pull request?** I’d welcome examples from real review workflows in the project’s issues. [Try MCP Bastion](https://github.com/lindseynguyen/mcp-bastion) · [Share feedback](https://github.com/lindseynguyen/mcp-bastion/issues)", "url": "https://wpnews.pro/news/what-mcp-bastion-found-when-i-changed-one-tool-description", "canonical_source": "https://dev.to/techsavant_lab/what-mcp-bastion-found-when-i-changed-one-tool-description-3gf", "published_at": "2026-11-11 03:40:03+00:00", "updated_at": "2026-11-11 03:51:12.005362+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "agent-protocols", "ai-safety", "developer-tools"], "entities": ["MCP Bastion", "Model Context Protocol", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/what-mcp-bastion-found-when-i-changed-one-tool-description", "markdown": "https://wpnews.pro/news/what-mcp-bastion-found-when-i-changed-one-tool-description.md", "text": "https://wpnews.pro/news/what-mcp-bastion-found-when-i-changed-one-tool-description.txt", "jsonld": "https://wpnews.pro/news/what-mcp-bastion-found-when-i-changed-one-tool-description.jsonld"}}