Matteo
Vincenti
Menu

MCP and skills: measure and reduce tokens without losing context

A reproducible MCP lab measures local token counts, tests filters and projections, and checks that shorter results preserve the evidence needed.

Two green cards in a compact holder beside a larger rack of ivory cards.

The example concerns an application's checkout service, which completes an order. In this lab, P1 labels incidents that block its release. The decision needs every open P1 incident in production, with an owner and an evidence reference. If the integration returns the whole incident register, the next step has to select the relevant records. Shorter tool descriptions leave that data volume unchanged.

I would start with the result contract: which records matter, which fields make them checkable, and how an incomplete retrieval is reported. In this lab, filtering and projection preserve the same answer while returning less material. The negative control also returns less data, misses a required incident and is rejected.

MCP and skills affect different parts of this process. MCP is the protocol a client uses to discover and call external tools; a skill contains instructions and resources for a task. The client decides how to present them to the model. That distinction helps locate the change before cutting text. The MCP guide covers how to build that connection.

The catalog, instructions and results have different costs

A tool exposes a name, description and argument schema. MCP defines operations such as tools/list and tools/call; loading definitions into a model's context is the host's responsibility. The 2026-07-28 Tools specification also supports catalog pagination. A cursor for application records returned by a query needs its own tool contract.

Deferred discovery loads definitions when needed. Claude Code documents it as the default, subject to model, deployment and configuration conditions. In Codex 0.164.0, exposure changes from Direct to Deferred when search_tool_enabled is active. Tool-search settings for a particular API belong to that surface; they are not configuration keys for every client.

A skill has another sequence: metadata for selection, its body when selected, and references when the task needs them. The Codex documentation separates the initial listing budget from reading the full SKILL.md. A shorter description can lighten the listing while making the skill harder to select if it loses the words that define its scope.

Then come the results: records, documents and logs. Discovery does not shrink an oversized result. Anthropic has already described filtering and processing data outside the model's context. Here I apply that principle to a checkable query: verify that the result remains sufficient before comparing quantities.

The same question, with an incident on the last page

The corpus is synthetic: 120 incidents, only two of them open P1 checkout incidents in production. The expected answer is fixed in expected.json: INC-017, owned by payments-oncall, and INC-088, owned by checkout-oncall. Each has its own evidence_ref. Distractors include similar incidents that are closed, in staging or assigned to another service.

The list_incidents tool accepts filters, fields, a cursor and a limit. A page contains at most 40 records. Each variant changes one choice:

  • A reads all three pages of full records, then filters in the driver.
  • B1 sends the filter to the server while retaining full records.
  • B2 adds projection of required fields: ID, severity, status, service, environment, owner and reference.
  • C stops A after the first page. It checks whether acceptance rejects an incomplete result.

These are B2's arguments, excerpted from the request. The package contains the complete implementation:

json
{
  "filters": {
    "severity": "P1",
    "status": "open",
    "service": "checkout",
    "environment": "production"
  },
  "fields": [
    "id", "severity", "status", "service",
    "environment", "owner", "evidence_ref"
  ],
  "limit": 40
}

The response includes records, next_cursor, total_matching, partial and errors. The driver checks the entire traversal: the final cursor must be null, every page's declared total must match the received count, no page may be partial, and errors from every page must remain absent. A clean final page cannot erase an earlier error or partial result. It then compares the decision and normalized incidents with the expected answer. An empty list and a failed retrieval therefore lead to different outcomes.

The lab makes real MCP calls over stdio with the official client/server SDK 2.3.1. Its driver is deterministic; a model does not choose the calls. The measured environment was Linux arm64, Docker 29.9.2, Node 24.21.0 and Python 3.13.17. Three repetitions per variant produced byte-identical traces. The table reports local o200k_base counts of JSON-RPC lines.

The same question, with an incident on the last page
VariantTool callsRecords receivedTool-response tokensAll-message tokensOutcome
A — all pages, driver-side filtering31204462645476Accepted
B1 — server-side filtering128201516Accepted
B2 — server-side filtering and projection12274989Accepted
C — first page of A, then stop1401487215548Rejected: INC-088 missing

The table uses local o200k_base counts from tiktoken 0.14.0. Each UTF-8 JSON-RPC line, including its newline, is counted separately. The “All-message tokens” column sums both directions, including initialization, catalog retrieval, requests and responses. The server returns the result in both text content and structuredContent; the count includes both copies.

B1 removes records unrelated to the question. B2 removes investigation details that remain available at the source. C also returns less material than A, but never sees INC-088 and fails the acceptance contract.

The count drops sharply because this fixture selects two records out of 120. A bill or quota comparison needs service counters for the complete task, including cache, output, retries, corrections and any child agents. Human work should be recorded separately. This lab counts exchanged material, rather than the prompt constructed by Codex or Claude.

Run the lab with the complete files

The attached package contains all 15 files, including the Dockerfile, dependency locks, server, fixture, driver, counter, expected answer and skill. The README is the entrypoint. No API key or host package installation is required.

mcp-token-contract.zip ↓

You need Docker Engine or Docker Desktop with Buildx, Git, a shell and unzip. The build downloads images, public packages and tokenizer vocabulary; the subsequent run has networking disabled. Base images are pinned by digest and dependencies by version. Create an empty working directory, save mcp-token-contract.zip there and open a terminal in that directory. git init prepares the local repository for the skill example too. Extraction creates examples/mcp-token-contract/.

bash
git init
unzip mcp-token-contract.zip
mkdir -p .cache/editorial/token-contract-run
docker buildx create --name token-contract-builder --driver docker-container
docker buildx build --builder token-contract-builder --load --progress plain \
  --tag token-contract:lab examples/mcp-token-contract
docker run --rm --name token-contract-run --network none --memory 512m --cpus 1 \
  --mount "type=bind,src=$(pwd)/.cache/editorial/token-contract-run,dst=/out" \
  token-contract:lab

Run these commands from the directory containing examples/. Choose a fresh output directory if you have an earlier receipt to retain. The process succeeds when A/B1/B2 match the expected answer, C misses INC-088 and is rejected, and the counts remain stable.

Under .cache/editorial/token-contract-run/, inspect report.json, manifest.json, the hashes, normalized answers and *.wire.jsonl traces. Protocol lines are preserved as base64 to retain their original bytes; the report already provides per-message counts. B2-1.evidence.json supplies the data for the skill example.

A short skill must retain its reading conditions

The packaged skill assesses that report. Its essential rules stay in the body; waiver handling lives in the supplied references/waivers.md. That reference specifies required fields, scope, expiry and approval evidence. A waiver is described separately while preserving the incident decision.

This is the complete SKILL.md, written in English for reuse in both clients:

markdown
---
name: release-readiness
description: Assess checkout release readiness from an incident-query report. Use when asked for a release decision based on incident evidence, including a proposed waiver.
---

# Assess the provided incident report

Use the report supplied by the user; do not infer incident state from this skill.
Check that the report covers open P1 incidents for checkout in production.

Keep these rules in every assessment:
- A complete traversal ends with `next_cursor: null`, includes every declared matching record, has `partial: false`, and has no retrieval errors. Otherwise return `INCOMPLETE` and name the missing evidence.
- Every blocker must include its ID, severity, status, service, environment, owner and evidence reference. Missing fields make the assessment incomplete.
- Return `HOLD` when a complete report has at least one matching P1 incident. Return `CLEAR` only when a complete report has none. These labels assess the supplied evidence; they do not execute or authorize a release.
- Preserve all blockers in the output. A proposed waiver cannot silently remove one.

Read [references/waivers.md](references/waivers.md) only when the request proposes a waiver or the report includes one. Apply its checks and report the waiver separately from the incident decision. For a standard assessment with no waiver, use the rules above without loading the reference.

Return the decision, completeness evidence, blocker IDs with owners and references, and any unresolved requirement. If the report is missing, ask for it instead of inventing an assessment.

The structure follows progressive disclosure, also discussed in the video Seven rules for skills, checked against current documentation. The reading condition matters: without a waiver, the body's rules suffice; a proposed waiver also requires the reference.

The two prepared text paths contain 308 local tokens for SKILL.md and 490 for SKILL.md plus the reference. A monolithic version that always contains both has the same 490 tokens. These are counted concatenations, not observations of what a client actually reads. When the reference is needed, its cost remains.

To try it in a local repository, choose your client from the same working directory used for the lab. For Codex CLI/IDE:

bash
mkdir -p .agents/skills
cp -R examples/mcp-token-contract/skill/release-readiness .agents/skills/

For local Claude Code:

bash
mkdir -p .claude/skills
cp -R examples/mcp-token-contract/skill/release-readiness .claude/skills/

Copy the directory only if no skill with that name already exists. Start the client in the repository. Invoke $release-readiness in Codex; start a Claude Code message with /release-readiness. Claude Code's locations distinguish local sessions, cloud sessions and plugins.

A first prompt can ask: “Assess .cache/editorial/token-contract-run/B2-1.evidence.json; no waiver is proposed.” The second adds a proposed waiver for INC-017 and an explicit assessment time, requiring the reference path. If an approver, expiry or required evidence is missing, the skill should retain those unresolved requirements. The lab counts the files in each path; executing these prompts in a client is a further test.

Where I would change a real integration

When the filter is known before reading, I would put it in the tool. If the result retains many fields unrelated to the decision, I would add projection while preserving identity, classification and evidence. For skills, I would keep standing conditions in the body and state when to open the details.

I would change discovery configuration when the observed problem is the definitions exposed at the start. A small catalog used almost in full may justify eager loading: search adds a step. OpenAI Tool search describes that alternative for its API surfaces.

A CLI that filters before printing is another option. Playwright CLI's maintainers recommend it for coding agents while retaining MCP for other interaction loops. I would compare output and integration maintenance; a script can also sit behind an MCP tool.

The check I would keep in every variant is the one for INC-088: a smaller retrieval must still find the evidence that changes the decision.

After retaining the results, remove only the lab's resources:

bash
docker buildx rm --force token-contract-builder
docker image rm token-contract:lab
rm -r .cache/editorial/token-contract-run

The container uses --rm. Base images may serve other work. This example's cleanup covers its dedicated builder and cache, lab image and the output directory you created.

Share this article

Have a question or a comment about this article?

Message me on LinkedIn
All articles