Reliability & Consequence 14 September 2026 8 min read 1,814 words

Nothing in the bytes says skill

The Model Context Protocol's Skills extension went Final this month, letting a server ship a folder of instructions into an agent's host. The same bytes are inert documentation or an active instruction set depending only on which path fetched them — and every rule that keeps the two apart is a rule for somebody else to implement.

The argument

The Skills extension makes activation rather than content the unit of trust, so the safety of server-shipped instructions rests on a distinction that exists only in the host's bookkeeping, and no shipping client has finished writing that bookkeeping yet.

Two calls, one URI. An agent asks its host to read skill://acme/billing/refunds/SKILL.md through a general-purpose resource tool and gets a markdown file: some YAML at the top, a few hundred lines of prose underneath about how this company handles refunds. Then it asks for the same URI through the host's skill-loading path. It gets the same bytes — but now the host has verified them against a manifest, checked a stored user approval, and opened a window during which those instructions govern what the agent does next.

The file did not change. The server did not change. The message on the wire is resources/read both times.

That difference became normative on 11 September, when SEP-2640 was marked Final and the Skills extension entered the Model Context Protocol as io.modelcontextprotocol/skills. The proposal was created on 23 April, accepted by the Core Maintainers on 3 September, and had its last wording commits on 13 September. What it standardises is modest on its face: a skill is a directory containing a SKILL.md, the format is delegated wholesale to the Agent Skills specification, and MCP carries it over the Resources primitive it already had. Three methods are added — skills/list, skills/get, and an optional resources/directory/read — and that is all. The extension's own backward-compatibility note says a client that does not implement it "sees skill:// resources as ordinary resources, which they are."

Which they are. That clause is the whole problem, and the specification is more honest about it than most standards manage to be.

Because what this extension actually ships is not a new data type. It is a new unit of trust, and the unit is activation rather than content. The stable text spells it out: "Reading a SKILL.md via resources/read does not by itself activate the skill. resources/read is transport." A skill becomes a skill only when the host routes the read through its own loading path, and the specification forbids the shortcut in the strongest terms it has: hosts "MUST NOT treat a resources/read of a SKILL.md that arrives by any other route as a load. Such a read grants no approval, opens no acting window, and confers no standing on the skill's supporting files." Nor can the URI settle it. A host "MUST NOT conclude that a resource is a skill merely because its URI carries a particular scheme." Skill-ness is out of band by design: it is established by a listing entry or by skills/get, never by inspecting the file.

This is a coherent design, and I think it is close to the only one available. But notice where it leaves the boundary. The property that determines whether a block of server-authored prose is documentation or direction is not in the prose, not in the transport, not in the metadata, and not in anything the server does. It exists solely in the host's record of how the bytes arrived. Everything downstream of that record — including the model, which is the component that acts — sees text.

The distance from a tool call is worth being precise about, because the industry has spent two years learning to reason about MCP in terms of tools. A tool call is legible in every direction: it has a name, a JSON schema, typed parameters, a result, and an effect that happens on the server, where the server's operator is accountable for it. The call appears in a log. Skills invert all of it. The effect happens on the host; the payload is prose typed as text/markdown; and the mechanism of action is that a model reads it and behaves differently afterwards. The specification states the asymmetry itself: "Unlike a remote tool call, an MCP-served skill can place server-authored bytes on the host and direct the model to execute them with host-side tools," and requires hosts to treat skills as "a higher-risk surface than remote tool invocation."

Follow that admission into the Security Considerations and the shape of the thing becomes clear. That section carries fourteen hard requirements — nine MUST and five MUST NOT — and every single one is addressed to the host. Not one imposes an obligation on the server. Hosts must treat skill content as untrusted input. Hosts must tag it with its originating server at the point it enters model context. Hosts must gate host-side code execution behind per-skill approval, must bind resource reads to the originating server, must resolve names in a per-origin namespace so a malicious server cannot publish a skill under a popular one's name, must obtain fresh consent before activating a nested skill, must keep the on-disk cache write-isolated or re-hash it on every access. The server's side of the contract, in that section, is nothing.

The document is not naive about its own mechanisms, either. It hands out SHA-256 digests per file and then disarms them: digests "are unsigned and come from the same server as the content," so a match proves consistency, not trustworthiness, and hosts "MUST NOT treat a digest match as a security boundary." It defines content-bound approval, in which a persisted consent binds to the exact set of URIs and hashes seen at the moment of approval, and any later change revokes it — consent to a set of bytes rather than to a capability, which is an unusual and rather principled thing to write down. Then it concedes the escape hatch: a server may declare "resources": "dynamic", publishing a skill with no integrity story at all. And it catches the sharpest detail of the lot. allowed-tools, a frontmatter field a local skill uses to declare which tools it needs, means something different when the same YAML arrives over a network: "A remote server populating allowed-tools is requesting elevated access on the host, not declaring a property of its own environment." Identical field, identical bytes, opposite meaning — resolved, again, by how it got there.

The credible objection came early, from the working group's own record. "If a user registers an MCP server, they are already extending their trust boundary," one contributor wrote in the open-questions document. "A malicious server can do far worse via tools than via a 'skill' document." That is true and it is the strongest thing said in the thread. A server whose tools you have approved can already act; prose is a weaker instrument than execution, and treating a markdown file as more dangerous than a function call gets the ordering wrong.

It gets it wrong only for the malicious server, though. The interesting case is the reputable one — a vendor's official server, connected on purpose, whose skill catalogue is edited by more people than its tool surface and reviewed by fewer. A tool's blast radius is bounded by what that tool does. A skill's is bounded by what the host will let the model do next, which is everything the user has already approved, for as long as the acting window stays open. The two are not the same shape, and the specification's own risk ranking agrees.

There is also a reason the extension had to be built this way, and it undercuts the objection from a different side. Resources were always application-controlled, and the working group recorded that as the thing holding them back: practitioners "have seen the 'application controlled' part as reducing their practical use." Skills are attractive precisely because they are model-controlled by default — the model reads a short description, decides the skill is relevant, and pulls it in. The control model was deliberately moved out of the application. The security section then asks the application to police a decision it was redesigned to stop making. One small piece of evidence that this tension was felt: SEP-2640, as accepted, contains the sentence "The model, not the host, decides whether to follow a skill's instructions." The stable specification keeps the origin-tagging requirement that sentence was there to justify. It does not keep the sentence.

None of this is sloppiness. The record shows a group that took the risks seriously: archive distribution was cut in July because safely unpacking a remote server's tarball means defending against decompression bombs, path traversal, symlinks escaping the directory, and Unicode collisions that silently overwrite SKILL.md — "an attack surface disproportionate to the benefit," and a judgement I would have struggled to make as cleanly. The care is real. It is just all pointed at one party.

Which brings the argument to the part an architect can check today. Every one of those fourteen requirements is a host requirement, and the community-maintained support matrix, last updated on 8 September, lists no client with full Skills support. Three are marked Partial: ChatGPT, fast-agent, and the MCP Inspector. Meanwhile the server side is buildable now — the SEP lists a prototype server implementation in the GitHub MCP Server, and reference implementations across the Python, C# and Go SDKs. Distribution ships on the schedule of the party that benefits from it. Enforcement ships on the schedule of the party that carries the risk. That gap is not a scandal; it is simply how security debt is manufactured, and it is visible in a table anyone can read.

So the question to put to a host vendor is no longer whether it supports skills. It is which of the fourteen it implements, what its skill cache does when a tool the model is running writes into it, and what a user sees when a connected server publishes a skill named the same as one of theirs. And the question to put to yourself, before connecting a server, is a slightly colder one than it used to be. You are not only granting that server the right to be called. You are granting it a channel into the instruction stream of whatever your agent does next.

Everything above is read from one party's own record — the specification, its proposal, its working group's notes. That is the right record for this argument, since the claim is about what the standard says rather than about how anyone has behaved. But it is worth naming: there is no independent implementation review here, because there is not yet enough implementation to review.

The extension is safe on the grounds that a skill is only data, carrying no more authority than any other text in the context. It is worth shipping on the grounds that the model will treat it as considerably more than that. Both are true. The distance between them is a host's bookkeeping — and by the time the bytes reach the component that acts on them, the bookkeeping is a line of text saying where they came from, in the same channel as everything else.

What this is argued from

Reporting and primary material the piece rests on, dated at the time of writing. The interpretation is mine; the facts belong to these.

  1. Skills over MCP, stable specification Model Context Protocol · 2026-09-10
  2. SEP-2640 Skills Extension Model Context Protocol · 2026-09-13
  3. SEP-2640 commit history GitHub · 2026-09-13
  4. Skills extension overview Model Context Protocol · 2026-09-13
  5. Extension support matrix Model Context Protocol · 2026-09-08
  6. Skills over MCP, open questions Model Context Protocol · 2026-08-28
  7. Skills over MCP, problem statement Model Context Protocol · 2026-08-28
  8. The 2026-07-28 Specification Model Context Protocol · 2026-07-28

Editorials on this site are written to be argued with. If you think the reading is wrong, it probably is in some particular way, and that is the useful part.

protocol designagentsprompt injectiontrust boundariesmcp