Workbench and Notebook: Why MCP Servers and Agent Skills Are Not Rivals

A workshop has three things in it: a craftsman, a workbench, and an apprenticeship notebook.

The craftsman is the model. Fast, general-purpose, capable of turning raw text into plans, code, and prose — but, on his own, he is only as good as his training. Give him a problem he has never seen and he will improvise, which is a polite way of saying he will guess.

The workbench is MCP. Power tools bolted down to the bench — a drill, a bandsaw, a calculator, a live wire to GitHub, a connection to your database. Each tool does one thing. Each tool is discoverable by name, has a typed input, and produces a typed output. The craftsman reaches for a tool when he needs to act on the world: query the database, fetch the file, send the email, run the calculation. The workbench is the outside — live, stateful, replaceable. Swap the bench for a different one and the craftsman can do a different kind of work without changing who he is.

The apprenticeship notebook is Agent Skills. The notes left by every master who has worked at this bench before — the house style guide, the recipe for filling out a PDF form, the multi-step procedure for posting a code review, the company-specific way of writing a commit message. The notebook is read on demand. The craftsman does not memorise every page; he opens the right chapter when the task matches. The notebook is the inside — codified, version-controlled, shareable across craftsman after craftsman.

Now imagine the three failure modes.

Failure mode 1: craftsman with a full workbench, no notebook. He has the drill but no idea which drill bit to use, or that he should measure twice before drilling once. He hammers when he should screw. He calls gh list_prs seventeen times in a row because he has no procedure for “this is what a code review looks like at our company.” Powerful, dangerous, expensive.

Failure mode 2: craftsman with a thick notebook, no workbench. He knows every procedure in the book — fill out a form, post a review, write a release note — but every procedure ends with “then call the gh API.” And he has no gh API. He writes beautiful prose about what he would do if he had tools. Helpful in theory, impotent in practice.

Failure mode 3: craftsman with neither. A bare LLM. He can pattern-match and improvise. Some of the time, that is enough. Most of the time, the improvisation is wrong in ways you cannot detect, because — as last month’s post on sycophancy spent three thousand words explaining — the model agrees with whatever shape of question you ask it, and the shape of the question is the entire signal it has.

The interesting design space is the craftsman with both. That is the modern agent stack: a model, an MCP workbench full of typed tools, and a Skills notebook full of codified procedure, composing in real time. This post is about why those two systems — MCP and Skills — are not rivals, how they actually differ under the hood, and a decision procedure for reaching for one, the other, or both.

🔧A 30-second preview, in two screenshots

MCP is a runtime protocol. The model discovers tools by sending tools/list over JSON-RPC. It calls a tool by sending tools/call with a JSON payload that matches the tool’s schema. The server runs the tool and returns a typed result. The round trip is hundreds of milliseconds. Tools are executable.

Skills is a folder format. A directory with a SKILL.md whose YAML frontmatter has name and description. At agent startup, every skill’s name and description are loaded into the system prompt. When a task matches a description, the agent reads the full SKILL.md into context and may also execute bundled scripts. No network round trip — the skill is on disk. Skills are codified.

If MCP is the workbench, Skills is the notebook — and the notebook now ships tools under scripts/ as well as recipes under SKILL.md. Different shapes of information. Different latency profiles. Different failure modes. Same agent.

The Source of the Confusion

Both standards have a single letter in their names that does a lot of work: S. Server in MCP, Skills in Agent Skills. Both are described as ways to “extend what an LLM can do.” Both are supported by Claude, by VS Code, by Cursor, by a growing list of AI clients. A reasonable engineer, on a Tuesday morning, scrolling through release notes, will read “MCP server” and “Agent Skill” and conclude that they are two competing ways to solve the same problem.

They are not. They solve different problems.

Here is the cleanest one-line distinction I have found:

MCP answers: “Can the model reach the outside world?” Skills answers: “Does the model know how to work?”

Stated as a matrix:

A yes to the first question gives the model new hands on the wire. A yes to the second gives the model new training on disk — procedure plus, optionally, scripts that reach the world without going over a wire. The two compose, but they are not the same shape of extension. The rest of this post is unpacking that distinction, then showing you the practical implications.

What MCP Is (and Is Not)

MCP (Model Context Protocol) is an open standard — originally released by Anthropic in November 2024, now stewarded by a community working group — for connecting an AI application to external systems. Anthropic’s own description is the cleanest: “MCP is a USB-C port for AI applications.” One standardised socket, many possible peripherals.

The architecture is a client-server protocol, layered:

flowchart TB Host["🏠 MCP Host<br/>(AI Application<br/>e.g. Claude Code, VS Code)"] subgraph "MCP Clients (one per server)" C1["Client 1"] C2["Client 2"] C3["Client 3"] end S1["🛠️ MCP Server A<br/>(e.g. Filesystem)"] S2["🗄️ MCP Server B<br/>(e.g. Postgres)"] S3["🌐 MCP Server C<br/>(e.g. Sentry, remote)"] Host --> C1 Host --> C2 Host --> C3 C1 ---|"JSON-RPC<br/>stdio"| S1 C2 ---|"JSON-RPC<br/>streamable HTTP"| S2 C3 ---|"JSON-RPC<br/>streamable HTTP"| S3 style Host fill:#e3f2fd style C1 fill:#fff3e0 style C2 fill:#fff3e0 style C3 fill:#fff3e0 style S1 fill:#c8e6c9 style S2 fill:#c8e6c9 style S3 fill:#c8e6c9

The protocol has two layers. The transport layer is either STDIO (a local subprocess the host launches and talks to over stdin/stdout — minimal overhead, single client, trust boundary is the OS process model) or streamable HTTP (a remote server reachable over the network, with bearer-token or OAuth auth — many clients, real latency, a protocol-level security boundary). The data layer is JSON-RPC 2.0 — a battle-tested wire format from before LLMs were a thing, with method names, IDs, and a notification channel for one-way pushes.

On the remote HTTP transport, the authn model is what makes MCP usable for sensitive actions. The host performs a token exchange with an authorization server — Authorization Code + PKCE for user-delegated flows, client credentials for service-to-service — and presents a scoped bearer token on every request. The MCP server runs each tool call with that scoped token, not with the user’s raw credentials and not with some host-wide service account. Tokens can be rotated, revoked, and audited. A token whose scope is repo:read cannot, by construction, push to a repo. The credential never sits in the model’s context window, never sits in a Skill folder, and never needs to be pasted into a config file. That is the security boundary Skills do not have.

What flows over the wire are primitives. Three kinds of thing an MCP server can offer to the client:

  • toolsexecutable functions the model can invoke. A tool has a name, a description, and a JSON Schema for its input. Calling a tool returns a typed result. This is the most-used primitive. Examples: gh_create_pr, sql_query, send_email, get_weather.
  • resourcesread-only data sources the model can pull into context. Distinct from tools because reading a resource has no side effects and is conceptually “fetch the file” rather than “run the function.” Examples: a project’s README.md, the schema of a database, today’s calendar.
  • promptsreusable templates the model or the user can invoke with arguments. Examples: a commit_message prompt that takes a diff and emits a conventional-commit message; a summarise_meeting prompt.

Three kinds of thing an MCP client can offer to the server (the inverse direction):

  • sampling — the server can ask the client to call the host’s LLM for a completion. Useful when the server wants LLM help without bundling a model SDK.
  • elicitation — the server can ask the user a structured question. Useful when the server needs confirmation before a side effect (“are you sure you want to delete the production database?”).
  • logging — the server can push structured log messages back to the client for debugging.

The protocol is stateful — both sides negotiate capabilities at startup (initialize / notifications/initialized), and servers can push real-time notifications when their tool list changes (notifications/tools/list_changed). A live server updating itself — for example, a file-system MCP server adding new tools as the user cds into a new project — is a first-class feature, not a bolt-on.

Security and Human-in-the-Loop

Two primitives make MCP the right shape for sensitive actions: scoped authentication and structured human confirmation. Skills define neither.

Authentication. On the streamable-HTTP transport, the host obtains a scoped token from an authorization server and presents it on every request. The server runs each tool call under that scope. A token whose scope is repo:read cannot push; a token whose scope is db:SELECT users cannot DROP TABLE users. Tokens are revocable, rotatable, and auditable. None of this requires the credential to enter the model’s context or sit in a Skill folder. Stdio MCP inherits the OS process model — no protocol-level authn, but the subprocess has whatever the launching user has, which is its own kind of trust boundary.

Human-in-the-loop via elicitation. MCP servers can pause a tool call to ask the user a structured question before completing a side effect. The server sends a schema (the question, allowed answers, validation rules); the host renders it; the user answers; the result comes back as a typed JSON value. This is the protocol-level answer to “are you sure?” — not a system-prompt instruction the model can be talked out of, but a wire-protocol step the server enforces.

// Server → client: ask the user a structured question before merging
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "elicitation/create",
  "params": {
    "message": "Merge PR #482 to main?",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "confirm": { "type": "boolean", "title": "Yes, merge" },
        "reason": { "type": "string", "title": "Reason (optional)" }
      },
      "required": ["confirm"]
    }
  }
}

The server’s merge_pr tool does not run until the user answers confirm: true. A Skill that says “do not merge without confirmation” is a sentence the model can be talked out of; an elicitation/create is a step in the wire protocol the server enforces. That is the difference between asking and checking.

Pre-approval, scoped credentials, and the inverse problem. The same reasoning applies in inverse: when a Skill prompt pre-approves certain actions (“you may run gh pr list and gh pr diff without asking”), the model is making the classification decision — and that classification is subject to the same sycophancy and prompt-injection failure modes as any other next-token prediction. A Skill can document which actions are safe to pre-approve; only the protocol can make pre-approval a property of the wire. The mechanism is a scoped credential: a token whose repo:read scope cannot, by construction, push; an MCP tool whose implementation refuses anything outside its allow-list. The Skill says “this is safe to skip the check”; the MCP server says “this check is unreachable from here.” Two layers, two guarantees, same pattern.

sequenceDiagram participant U as User participant A as Agent participant MC as MCP (token scope: repo:read) U->>A: List open PRs A->>MC: tools/call list_prs MC-->>A: 3 open PRs (#482, #485, #491) A->>U: 3 open PRs U->>A: Merge PR #482 A->>MC: tools/call merge_pr MC-->>A: 403 (scope insufficient) Note over A,MC: No confirmation, no retry —\nthe action is structurally impossible

Together, scoped credentials and elicitation are the two protocol-level answers to the question this post keeps returning to: how do you give the model a reason to do something other than agree with the user?

🛠️What a tool call actually looks like on the wire

A simplified exchange, taken straight from the MCP architecture docs. Two JSON-RPC 2.0 messages, request then response:

Request — client to server:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "weather_current",
    "arguments": {
      "location": "San Francisco",
      "units": "imperial"
    }
  }
}

Response — server to client:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Current weather in San Francisco: 68°F, partly cloudy…"
      }
    ]
  }
}

That is the entire protocol surface for using a tool. The interesting design choices — typed schemas, content arrays, capability negotiation — are all in service of this one thing: the model invokes a typed function, gets a typed answer back, in a portable wire format.

What MCP is not:

  • It is not a prompt. You cannot write system: you are a polite assistant and call it MCP. MCP is a runtime protocol for actions and data, not a way to steer generation.
  • It is not a fine-tune. MCP servers do not change the model’s weights.
  • It is not a system-prompt override. The host’s system prompt is the host’s business; MCP is what the host can reach when the model decides to act.
  • It is not an Agent Skill. We will get there in the next section.

What Skills Are (and Are Not)

Agent Skills are an open standard released by Anthropic on 16 October 2025 as a Claude-platform feature, then published on 18 December 2025 as an open cross-platform standard at agentskills.io. The format is refreshingly boring: a folder, with one required file.

my-skill/
├── SKILL.md          # Required: YAML frontmatter + instructions
├── scripts/          # Optional: executable code the agent may run
├── references/       # Optional: docs the agent may load on demand
└── assets/           # Optional: templates, schemas, images

SKILL.md must begin with YAML frontmatter containing at minimum a name and a description. The body of the file is plain markdown — instructions, examples, recipes, what to do, what not to do. A complete Skill, ready to ship, can be a single file under 50 lines. A complex Skill might bundle 200 lines of Python and three reference documents; the protocol does not care.

The interaction model is progressive disclosure in three stages:

flowchart TD S0["🟢 Stage 1 — Discovery<br/>name + description only<br/>loaded into system prompt<br/>at agent startup"] S1["🟡 Stage 2 — Activation<br/>full SKILL.md body<br/>read into context<br/>when description matches task"] S2["🔴 Stage 3 — Execution<br/>agent runs bundled scripts<br/>and/or loads referenced files"] S0 -->|"task matches<br/>description"| S1 S1 -->|"script or reference<br/>needed"| S2 style S0 fill:#c8e6c9 style S1 fill:#fff9c4 style S2 fill:#ffcdd2

Stage 1, Discovery. When the agent starts up, it loads the name and description of every installed skill into its system prompt. That’s it. A handful of hundred-byte lines. The agent now knows the skill exists and what it is for, but it has not yet read the body.

Stage 2, Activation. When a user message arrives, the agent’s reasoning decides whether the task matches any skill’s description. If yes, the agent reads the full SKILL.md into context. This is where the procedural knowledge lands — the recipe, the procedure, the house style guide.

Stage 3, Execution. If the SKILL.md references bundled scripts or reference files, the agent may invoke them. A Skill can ship executable code that the agent runs deterministically — same inputs, same output, byte-for-byte, on every machine and every model. This is the part that surprises people: a Skill is not just instructions. It is instructions plus code-as-tool. The instructions tell the agent when and why; the code tells the agent how, reliably, the same way every time. Where the rest of the agent is a next-token sampler that can be steered by a leading question, a bundled script is a function call: it returns what the code says it returns, and the model’s sycophantic prior cannot argue with it. That is the determinism claim, and it is the load-bearing reason to ship a script instead of describing the procedure in markdown.

The three stages compose. The agent starts knowing about every skill by name. It loads a skill’s instructions only when relevant. It executes a skill’s bundled code only when needed. The context window cost of having a hundred skills installed is — at idle — the cost of a hundred name + description pairs, not the cost of a hundred full manuals.

Adoption. Skills started as a Claude feature and grew fast. The agentskills.io directory at the time of writing lists roughly forty adopting clients, including Claude Code, Cursor, VS Code, GitHub Copilot, OpenAI Codex, Gemini CLI, OpenHands, Junie, JetBrains’ Junie, Spring AI, Laravel Boost, Snowflake Cortex Code, Databricks Genie Code, and many more. When forty tools agree on a folder format with a SKILL.md file, the format has won — you can write a Skill once and use it across your stack.

Bundled Scripts: Configuration and Determinism

This section is the one I would have liked to read before writing my first non-trivial Skill, because it closes the gap between the “Skill = markdown” framing and what real Skills actually do. A Skill folder is allowed to ship executable code under scripts/, and once it does, two things become possible that the markdown alone cannot give you: connection to live systems and deterministic answers.

Connection. A script in scripts/ is just a program. It can import any library its host runtime provides — a Postgres driver, an HTTP client, an SMTP library, an SSH shell, a Redis client — and use it to reach the world. The markdown does not connect to anything; the script does. So when someone asks “can a Skill talk to my database?”, the answer is “the Skill’s markdown cannot, but a Skill that ships a script whose code opens a connection absolutely can.” The markdown tells the agent when and why; the script tells the agent how, and reaches the database itself.

Configuration. A script that talks to a database, sends mail, or calls an internal API needs configuration — connection strings, credentials, tenant IDs, feature flags. The Skill format deliberately does not invent a configuration protocol; the script reads configuration the way any other program does:

  • Environment variables read at runtime (os.environ["PII_DB_URL"]). The agent runtime injects them; secrets stay out of the Skill folder. This is the most common path.
  • A config file in the Skill folder (scripts/db.yaml, .env.example, references/connection.md). The agent reads it once and passes paths into the script as arguments. Use this when the configuration is per-Skill, not per-host.
  • Arguments passed by the agent (python scripts/query_db.py --tenant=acme --sql="SELECT 1"). Use this when the procedure’s parameters are user-driven and change per invocation.

All three paths are just plumbing. What they have in common is that the script gets the inputs it needs to be deterministic and reach the right system.

A worked example. Imagine a pii-audit Skill whose job is to flag unknown email addresses in a body of text. The Skill ships scripts/check_pii.py, which connects to the canonical-emails table in the production database, runs a deterministic regex over the input, and exits with a count and a list of unknown emails. The configuration is the PII_DB_URL env var. The Skill’s SKILL.md instructs the agent to invoke the script via the Bash tool whenever the user asks “are there any unknown emails in this document?”.

pii-audit/
├── SKILL.md
└── scripts/
    └── check_pii.py
# scripts/check_pii.py — bundled with the Skill folder
import os, re, sys, sqlite3

EMAIL_RE = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")

def main() -> int:
    text = sys.stdin.read()
    db_url = os.environ.get("PII_DB_URL")
    if not db_url:
        print("error: PII_DB_URL not set", file=sys.stderr)
        return 2
    conn = sqlite3.connect(db_url)
    canonical = {row[0] for row in conn.execute(
        "SELECT email FROM canonical_emails"
    )}
    conn.close()
    found = set(EMAIL_RE.findall(text))
    unknown = sorted(found - canonical)
    if unknown:
        for e in unknown:
            print(f"unknown_email: {e}")
        print(f"summary: {len(unknown)}/{len(found)} emails not in canonical list",
              file=sys.stderr)
        return 1
    print(f"ok: all {len(found)} emails canonical", file=sys.stderr)
    return 0

if __name__ == "__main__":
    sys.exit(main())

Read the script twice. Notice three things:

  1. It connects to a database. The skill’s markdown did not connect to anything; the script did. The Skill folder, end-to-end, can talk to Postgres, MySQL, Redis, anything the host runtime can import.
  2. Its output is deterministic. Given the same input text and the same canonical set, the script’s stdout is byte-identical on every run, on every machine, on every model. The regex is deterministic. The SQL is deterministic. The exit code is deterministic. The model’s sycophantic prior has nothing to grip onto because there is no sampling.
  3. The SKILL.md only steers when and why. It does not invent the procedure; it does not improvise the SQL; it does not decide whether an email is canonical. Those decisions live in code, and code is what makes the answer reproducible.

This is the pattern. The markdown is the recipe. The script is the cooking. A Skill that ships only markdown can teach the agent how to think but cannot guarantee a deterministic answer. A Skill that ships a script can teach the agent how to think and guarantee the answer, provided the configuration is wired up. The two together — procedural knowledge on disk plus deterministic execution on disk — are why a Skill folder is more than a fancy system prompt.

The Trust Boundary

Skills have no authentication model. They are files on disk. The agent reads them; that is the entire interface. There is no “Skill OAuth flow,” no scoped credential, no token refresh, no audit log, no human-in-the-loop checkpoint. The format deliberately does not invent one.

A Skill script that runs is a program. It runs as whatever user the host agent runs as, with whatever environment variables that user has, with whatever filesystem and network access that user has. The blast radius is identical to running bash in the agent’s shell. If the agent’s host process has AWS_SECRET_ACCESS_KEY in its environment, the Skill’s scripts/*.py has AWS_SECRET_ACCESS_KEY. There is no scope. There is no expiry. There is no revocation.

The implication is sharp:

  • Configuration lives in env vars (line 211) or config files (line 212) or arguments (line 213). Treat those secrets with the same care as .bash_history — because from the OS’s perspective, that is what they are.
  • If the action needs a scoped credential — read-only on this table, write-only on that bucket, time-limited, revocable — that part is not the Skill’s job. It is MCP’s. Authenticate the MCP server; let the Skill orchestrate the procedure; let the MCP tool do the work with a token that has a scope.
  • If the action needs a human in the loop — confirmation before delete, approval before send, double-check before deploy — the Skill’s SKILL.md can instruct the agent to ask, but the protocol does not enforce it. MCP’s elicitation/create enforces it. The Skill says “do not merge without confirmation”; MCP’s elicitation/create makes the merge tool refuse to run until the user answers.

This is the cleanest one-line contrast with MCP, and the cleanest one-line justification for composing the two:

A Skill teaches the agent when and why. MCP authenticates the execution and enforces the check.

Reserve Skills for the procedure; reserve MCP for the part that touches a sensitive system. The two compose; the trust levels do not.

🔧Reach for a Skill script whenever the answer must be the same every time

A regex check, a hash computation, a SQL count, a content-hash of a file, a JSON-schema validation, a CSV diff — any step whose correct answer is reproducible — belongs in a Skill script, not in markdown. The markdown can describe when to run it. The script runs the same way every time it is invoked. Use this distinction when reviewing a Skill you wrote or inherited: if you can write the step as a pure function, ship it as a script; if it requires judgement, ship it as markdown.

What Skills are not:

  • It is not a runtime protocol. There is no tools/list over JSON-RPC. The skill is on disk; the agent reads it. There is no server to spin up.
  • It is not an MCP server — the markdown of a Skill cannot, by itself, connect to a database, fetch a web page, or send an email. But a Skill — the folder — often can, by bundling a script whose code does the connecting (see the previous section). A Skill can also instruct the agent to call an MCP tool that does those things — and that composition is the most powerful pattern in the post, which we will get to.
  • It is not a fine-tune. A Skill changes the model’s conditioning context, not its weights.
  • It is not a system-prompt snippet in disguise. A Skill has its own discovery model, its own activation lifecycle, and its own bundled code. A 500-line system: block is not the same thing, even if it covers similar ground.

The Mechanism: Why Both Exist

This is where last month’s post becomes load-bearing.

In The Magic Mirror That Always Says Yes, the argument was that an LLM is a next-token predictor: it computes the probability of the next token, given everything that came before. Sycophancy falls out of this as a degenerate equilibrium: the model is rewarded, via RLHF, for agreeing with the user’s prior. The closing line of that post was:

“If you want it to tell you the truth, give it a way to find the truth that does not go through you.”

That is the operative sentence for understanding why MCP and Skills exist as separate standards.

MCP is a way to ground the truth in live external data. When the model calls sql_query and the database returns a row count, that row count is in the model’s conditioning context. The user-prior is still there, but the tool-output is also there, and the tool-output is not conditioned on the user’s belief. The next-token distribution, conditioned on a tool result, is biased toward the tool’s answer in a way the user’s prior cannot easily override. The model can still misread the tool — but it cannot pretend the tool did not say what it said. The truth comes back over a wire that does not run through the user.

Skills is a way to ground the truth in codified procedural knowledge. When the agent activates a Skill, the Skill’s instructions enter the conditioning context. The user-prior is still there, but now there is a competing prior — the procedure the Skill describes — which has been written by someone who has done this task before, often many times, and who has distilled the right way into a recipe. The next-token distribution, conditioned on a Skill body, is biased toward the Skill’s procedure in a way a user who has not seen the procedure cannot easily override. The truth comes from a manual that was written before the conversation started.

Both standards are, in their respective ways, anti-sycophancy infrastructure. They are different layers of the same defence:

Truth-grounding mechanismLayerDirectionLatency
MCP tool callLive external systemWire → contextHundreds of ms
MCP resource readRead-only external dataWire → contextTens of ms
MCP promptReusable templateDisk → contextNegligible
Skill activationCodified procedureDisk → contextNegligible
Skill script executionDeterministic code-as-toolDisk → process → contextTens of ms
Skill script + configDeterministic code-as-tool with parametersEnv / file / args → process → contextTens of ms

The May post argued that no prompt pattern alone fixes a sycophantic prior. The June post argues the same thing from the other side: no single layer of truth-grounding fixes the prior either. The modern agent stack — the craftsman with both workbench and notebook — is the composition of MCP and Skills, plus the inference-time patterns from the May post (self-consistency, debate, critique-then-revise), plus the system-level mitigations (Constitutional AI, structured output), plus a calibrated human in the loop.

🧠The pattern behind the pattern

Every anti-sycophancy intervention does the same thing under the hood: it adds an explicit prior to the model’s input context that competes with the user’s implied belief. MCP tools add a tool-output prior. MCP resources add a retrieval prior. Skill activation adds a procedure prior. Skill scripts add a deterministic-computation prior. Constitutional AI adds a principle prior. The user-belief prior is implicit but it is there. The interventions are ways of telling the model, in its own language: “do not condition on that one; condition on this one instead.”

Side by Side: The Same Task, Two Implementations

The cleanest way to feel the difference is to build the same toy task both ways. We will build a current time tool — the world’s least useful feature — once as an MCP server and once as a Skill. The toy is trivial; the shape of each implementation is the point.

Side A — The MCP server

A Python file that uses the official mcp SDK to expose a current_time tool. The model discovers the tool at startup via tools/list. When the user asks “what time is it?”, the model invokes the tool via tools/call. The server runs datetime.now() and returns a typed result.

# time_server.py — run with: python time_server.py
from datetime import datetime
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("time-server")

@mcp.tool()
def current_time(timezone: str = "UTC") -> str:
    """Return the current time in the given IANA timezone.
    
    Use this whenever the user asks for the current time or date.
    """
    return datetime.now().isoformat()

if __name__ == "__main__":
    mcp.run()

Thirty-three lines including the docstring. To use it, you register it with your MCP host — Claude Code, Cursor, VS Code, anything that speaks MCP — and the host spawns it as a subprocess. The tool becomes available immediately, typed-schema-validated, discoverable by name. Wire format: JSON-RPC over stdio.

Side B — The Skill folder

A directory with a SKILL.md and one bundled script. The agent reads the markdown when the user’s task matches the description, and runs the script when the procedure calls for it. There is no server. There is no JSON-RPC. The procedural knowledge — including the trick of using datetime.now().isoformat() — is on disk alongside the deterministic execution.

time-skill/
├── SKILL.md
└── scripts/
    └── now_utc.py
---
name: current-time
description: Return the current date and time. Use this whenever the user asks "what time is it?", "what's today's date?", or asks for a timestamp. Do not guess — invoke a tool or run a script.
---

# Current time

When the user asks for the current time or date, do NOT guess. Either:

1. **Preferred:** call a `current_time` MCP tool if one is available. The tool
   is typed and returns an ISO-8601 string.
2. **Fallback (bundled script):** run `scripts/now_utc.py` from the Skill folder
   via the Bash tool. The script reads no env, no stdin, no clock drift — it
   returns the current UTC time in ISO-8601 form, deterministically, on every
   machine in the same second.

   ```python
   # scripts/now_utc.py — bundled with the Skill folder
   from datetime import datetime, timezone
   if __name__ == "__main__":
       print(datetime.now(timezone.utc).isoformat())
   ```

3. **Last resort:** if neither tools nor bash are available, tell the user
   that you do not have access to a clock and ask them to confirm the time.

Never fabricate a timestamp. If you are not sure whether the user means
local time or UTC, ask. The default in this skill is UTC.

Fifty-five lines total, half of them inside the bundled script. The markdown biases the agent toward the right procedure; the script provides the deterministic execution when the MCP tool is unavailable.

🔧What changed — and why

Same fallback shape as the MCP version, but with one key difference: the fallback is now version-controlled code next to the procedure, not a one-liner pasted into the SKILL.md. The MCP server is the workbench; the Skill is the notebook — opened on demand, sometimes containing a chisel.

Same task. Two shapes. Different latency. Different failure modes. Different extension story. The MCP server can serve many agents simultaneously and survive a host restart. The Skill is per-host, per-folder, but travels with the codebase and survives any toolchain change.

⚠️The implementation does not tell you which to use

You can absolutely build the same production feature either way, with different trade-offs. The “time” example is a toy; in real systems the choice is non-obvious and depends on:

  • Does the feature need to live across multiple hosts? → MCP.
  • Does the feature need to live inside a repo, version-controlled with the code? → Skills.
  • Does the feature need to execute untrusted code? → MCP server in a sandbox.
  • Does the feature need to teach the agent when to do X? → Skills.

The next section is the decision procedure for getting this right.

Try It Yourself: A Bigger Side-by-Side

Both standards reward hands-on experimentation. Set aside twenty minutes.

🔧Build both versions of a tiny 'greet' tool

Part 1 — the MCP version (≈ 5 minutes):

  1. Install the official Python SDK: pip install mcp
  2. Save the time_server.py snippet above as time_server.py.
  3. Run it once: python time_server.py. It will idle on stdio. Press Ctrl+C.
  4. Register it with Claude Code: add the path to your .mcp.json.
  5. Ask Claude Code “what time is it?” and watch the tools/call round trip.

Part 2 — the Skill version (≈ 5 minutes):

  1. Create ~/.claude/skills/current-time/ (or the equivalent for your client).
  2. Drop the SKILL.md above into that folder.
  3. Restart your client so the skill’s metadata is loaded at startup.
  4. Ask “what time is it?” — the agent activates the Skill, runs the fallback one-liner, and returns the ISO-8601 timestamp.
  5. Verify: edit SKILL.md to change the default timezone to Asia/Tokyo. Ask again. The next answer reflects the new procedure without any server restart, because Skills are pull-on-demand from disk.

Part 3 — the combined version (≈ 10 minutes):

  1. Keep the MCP server running.
  2. Edit the Skill’s body to remove the fallback path: it should now only instruct the agent to use the MCP tool.
  3. Ask again. The Skill’s procedure says “use the MCP tool”; the MCP tool is available; the agent calls it.
  4. Now stop the MCP server (Ctrl+C). Ask again. The Skill’s procedure fails over to the bash one-liner.
  5. This is composition. This is the modern agent stack.

Compare what you felt across the three runs. The MCP version is fast and typed but feels remote — a wire between the model and the workbench. The Skill version is soft and procedural but executable where it matters — instructions in the agent’s head, and a deterministic script on disk when the procedure needs real-world reach. The Skill fallback is not a thought; it is code, and code is the same on every run. The combined version is both at once — the Skill knows what to do, the MCP knows how to do it fast, and the Skill’s fallback lets the system degrade gracefully when MCP is offline.

That triple — typed tools + procedural knowledge + graceful fallback — is what production agent teams actually ship. Everything else is dressing.

The Decision Matrix

Here is the procedure. Five rules, each one sentence, each tied to a specific shape of problem.

🧭Decision Matrix — five rules, ≤ 2 sentences each

Rule 1 — Reach for MCP when the agent needs live external data or side effects. If the answer must reflect the current state of the world (a database row, a file on disk, a GitHub PR, an API response, a user’s calendar), MCP is the right shape. The workbench is for acting on the world.

Rule 2 — Reach for Skills when the agent needs codified expertise the base model lacks. If the answer requires a procedure, a house style, a domain-specific recipe, or a checklist that has been refined by humans over time, Skills is the right shape. The notebook is for teaching the model how to work.

Rule 3 — Reach for both when a multi-step procedure calls external tools. If the procedure cannot complete without touching live systems — fill out a form by reading from the database, post a code review by calling gh, generate a report by querying an analytics API — compose them. A Skill orchestrates the procedure; MCP tools do the steps.

Rule 4 — Reach for an MCP resource (not tool) when the agent needs read-only context. If the data does not need to be acted on, only read into the model’s conditioning context, expose it as a resource. Resources have no side effects, are conceptually “fetch the file,” and avoid the tool-call overhead when all the model needs is the bytes.

Rule 5 — Reach for neither when a plain system prompt or fine-tune suffices. If the question is style — be more concise, use British spelling, never use the passive voice — write it in the system prompt. If the question is knowledge the model already has — general trivia, common programming patterns — do not pay for a Skill. Reserve both standards for the cases where they pay for themselves.

Rule 6 — Reach for a Skill script when the procedure needs a deterministic step. If a step in the procedure has a correct answer that must be the same on every run — a hash, a regex match, a SQL count, a JSON-schema validation, a content-fingerprint — ship it as a bundled script under scripts/ and have the SKILL.md tell the agent when to invoke it. Same inputs, same output, byte-for-byte. The model’s sycophantic prior cannot rewrite a deterministic return value. Use this rule every time you catch yourself describing in markdown a step you could have written as a pure function.

Rule 7 — Reach for MCP when the action needs scoped credentials or human confirmation. PII reads, prod writes, financial actions, anything destructive — MCP’s authentication boundary and elicitation channel are the right shape. A token whose scope is repo:read cannot push; a tool that calls elicitation/create before merge_pr cannot merge without the user’s confirm: true. A Skill script with os.environ["PROD_API_KEY"] shares the host’s blast radius; there is no scope, no expiry, no audit trail, and no enforced check. Authenticate the MCP server; let the Skill orchestrate; let MCP touch the sensitive system.

Rule 8 — A Skill script’s security boundary is the host process. Reserve credentialed execution for MCP. Use Skills for the procedure; use MCP for the part that authenticates and the part that checks. The two compose; the trust levels do not. If you can describe the action as “the model decides to do this dangerous thing”, it belongs behind MCP. If you can describe it as “the model knows the right procedure”, it belongs in a Skill. The dangerous thing is what needs a security boundary. The procedure is what needs codification. Pick the standard that matches the shape.

Rule 0 (load-bearing) — When in doubt, defer to the model’s prior. Both standards defer to the host’s reasoning. A Skill that the agent does not activate is a Skill that did not happen. An MCP tool the agent does not call is a tool that did not happen. Neither system overrides the model’s decision to use it. If the prior on “should I activate this skill” or “should I call this tool” is wrong, you have a system-prompt problem, not a Skill or MCP problem.

Read it twice. Save it in your team’s wiki. The next five agent features you ship will each slot cleanly into one of these rules.

The Composition Pattern: Skill-Orchestrated MCP

This is the section I have been building toward. The single most powerful thing you can do with the modern agent stack is compose the two standards so that a Skill orchestrates a procedure that uses MCP tools as its steps.

A worked example. Imagine a team that ships a code-review Skill and a github MCP server. The Skill is a folder with a SKILL.md that describes the team’s house review procedure — what to check, in what order, with what severity. The MCP server exposes list_prs, get_pr_diff, post_review_comment, and merge_pr as tools.

sequenceDiagram participant U as User participant A as Agent participant SK as Skill (code-review procedure) participant MC as MCP Server (github tools) U->>A: "Review PR #482" A->>SK: Activate Skill\n(description matches) SK->>A: Procedure:\n1. List open PRs\n2. Read diff\n3. Apply house style\n4. Post review\n5. Don't merge A->>MC: tools/call list_prs\n(filter: #482) MC-->>A: PR metadata A->>MC: tools/call get_pr_diff\n(pr_id: 482) MC-->>A: diff (8 files, +142/-37) Note over A: Reads house style\nfrom Skill's bundled refs/ A->>A: Apply procedure:\nstyle check + severity rubric A->>MC: tools/call post_review_comment\n(body, severity, line refs) MC-->>A: comment_id, url A->>U: "Posted review on PR #482:\n3 nitpicks, 1 must-fix." Note over A,MC: Procedure says "do not merge<br/>without confirmation" A->>MC: tools/call merge_pr MC-->>A: elicitation: "Merge to main?" A->>U: "Skill procedure: confirm merge?" U-->>A: "yes" A->>MC: tools/call merge_pr (confirmed) MC-->>A: merged, sha

Notice what the Skill does not do. It does not call gh. It does not parse the diff. It does not post the comment. All of that is MCP’s job. What the Skill does is tell the agent when and why — the procedure, the house style, the severity rubric, the “do not merge” guardrail. The agent follows the procedure by calling the right MCP tools in the right order.

Notice also what neither the Skill nor the agent gets to override: the merge_pr tool does not run until the user answers the MCP server’s elicitation/create request. The Skill’s procedure says “do not merge without confirmation”. The agent might be talked out of that sentence. The MCP server is not a sentence. It is a wire-protocol step. The procedure steers the intent; the protocol enforces the check. That is the load-bearing difference between instructing and checking, and it is why the two standards compose instead of substitute.

This is the pattern. Skills put the procedure on disk. MCP puts the workbench on the wire. Neither is sufficient alone. A Skill that ships only markdown can teach the agent how to work; a Skill that also ships scripts under scripts/ can reach the world without going over a wire at all. An MCP server can give the agent typed live tools shared across hosts. The procedure is codified and shareable; the execution is either on-disk-deterministic (Skill scripts) or on-typed-wire (MCP tools). The system as a whole degrades gracefully when either side is missing.

🏗️When you see a new agent feature, sketch the diagram first

Before writing any code, draw the four boxes: User, Agent, Skill, MCP. Ask four questions:

  1. What does the user say?
  2. What does the agent decide?
  3. What does the Skill teach?
  4. What does the MCP server execute?

If your sketch has empty boxes, you have not designed the feature yet. If your sketch puts everything in the Skill, you have not split the procedure from the execution. If your sketch puts everything in the MCP server, you have a procedure living in code that should be living in a notebook. The four-box sketch is the simplest way to keep the two layers from collapsing into one another.

The Deeper Point

Here is the part that no decision matrix can substitute for.

MCP and Skills are not rivals because they are not on the same axis. They are not two ways to solve the same problem; they are two layers of a single agent stack, the way HTTP and DNS are two layers of the internet. HTTP could, in principle, do its own name resolution. DNS could, in principle, carry payloads. They don’t, because each layer has its own shape, its own failure modes, its own lifecycle. The two layers compose into a system that neither could build alone.

The agent stack has the same structure. The model layer predicts the next token. The Skill layer conditions the prediction on codified procedure. The MCP layer conditions the prediction on live tool output. The user layer conditions everything on the user’s question. Each layer has its own shape, its own latency, its own failure mode. The agent is the thing that integrates them.

This is also why “which one should I use?” is the wrong question. The right question is “what is the shape of the extension my agent needs?” If the extension is executable infrastructure that reaches the outside world, MCP. If the extension is codified procedure that teaches the agent how to work, Skills. If the extension is a procedure that reaches the outside world, both. The question is not which one. The question is what shape.

And — to close the loop with last month’s post — both shapes are anti-sycophancy infrastructure. MCP is anti-sycophantic because the tool’s answer is in the conditioning context and cannot be argued away by a leading question. Skills is anti-sycophantic because the procedure is in the conditioning context and biases the next-token distribution toward what has worked before. Neither shape removes the model’s tendency to agree with the user. Both shapes give it a reason to do something other than agree. That is the whole point.

The Playbook, in One Frame

For the team whiteboard. Two axes, four quadrants, one overlay guard. Plot your task; read the quadrant; check the overlay. The decision is geometric, not procedural.

quadrantChart title Shape of the Extension x-axis "No live data" --> "Needs live data" y-axis "No codified procedure" --> "Needs procedure" quadrant-1 "COMPOSE" quadrant-2 "Agent Skill" quadrant-3 "System prompt / nothing" quadrant-4 "MCP server" "House style guide": [0.15, 0.85] "Domain recipe": [0.25, 0.9] "Code review procedure": [0.8, 0.85] "Generate report from DB": [0.75, 0.7] "Send email": [0.7, 0.2] "Query weather": [0.85, 0.15] "Be more concise": [0.1, 0.1] "Capital of France": [0.05, 0.05]

Sensitivity & HITL overlay. If your task lands in quadrant-1 or quadrant-4 and the action is sensitive (PII, prod, financial) or requires human confirmation before a side effect, the choice upgrades to MCP with scoped credentials and elicitation/create. The Skill says “this is safe to skip the check”; the MCP server says “this check is unreachable from here.” A Skill that documents which actions are safe to pre-approve is a sentence; a scoped token whose scope is repo:read cannot push.

Two axes and one overlay. The same shape-check that took 250 words to spell out linearly is one frame on the wall. Use the chart for the team conversation; reach for the Security and Human-in-the-Loop subsection above when the answer is sensitive, and the Composition Pattern section when the procedure needs orchestration.

Resources to Explore

The two standards are active, evolving, and well-documented. The following sources are the ones I keep open while designing an agent feature:

  • modelcontextprotocol.io — the canonical MCP spec, architecture overview, and SDK list. Start with the Architecture page; the JSON-RPC walkthrough is the cleanest primer on the wire format.
  • agentskills.io — the cross-platform open standard for Skills, with the full SKILL.md format spec and the adopting-clients directory (~40 at the time of writing).

Workshop Self-Test: Which Side Are You Under-Indexing On?

The failure modes are mirror-images. Some teams over-index on tools and ship agents that hammer the workbench with no idea what they are building. Some teams over-index on procedures and ship beautifully-written agents that cannot reach a database. Some teams forget the security layer entirely. A six-question self-test.

🧭Open the 6-question self-test

Open the 6-question self-test (works without JavaScript: count your own checkmarks and read the legend below)

Legend (works without JavaScript — count your checkmarks):

  • 0–2 points: Balanced. You reach for both layers when each is warranted and you put sensitive actions behind a security boundary. Keep doing this.
  • 3–4 points: Leaning one way, or missing the security layer. The “tools without procedure” failure mode (Q1, Q3) means you over-index on MCP; the “procedure without tools” failure mode (Q2, Q4) means you over-index on Skills. Checking Q6 means the security layer is missing entirely — your “ask first” lives in the model’s good intentions, not in the protocol.
  • 5–6 points: One-armed craftsman. You are missing the other layer entirely, or running without a security boundary. The fastest fix: take your next agent feature and force yourself to build both — one Skill orchestrating at least one MCP tool with elicitation before any destructive step.

🔧Final Thought

The craftsman was never the bottleneck. The workbench was not enough. The notebook was not enough. The interesting design — the one that has emerged in 2025 and stabilised in 2026 — is the craftsman at the bench with the notebook open beside him, executing procedures he did not improvise, against data he did not invent. MCP is the bench. Skills is the notebook — and the notebook now ships hand-tools too, bundled under scripts/. The craftsman reaches for whichever tool is closest to hand: a wire to the bench, or a chisel from the notebook. The agent is the craftsman. The shape of the work determines which standard you reach for, and how to compose them. The model is still, as it has always been, a next-token predictor — but now it is a next-token predictor that has been given two different ways to find the truth without going through the user. That is the whole trick.

Try the Workshop Self-Test. Then build the next agent feature with both shapes in mind, and see how cleanly the conversation collapses to “which layer does this belong in?”

Comments

Please accept the "Functionality" cookie category to view and post comments.