# Tools, not pages: MCP server cards, agent skills and WebMCP

> Reading a page is inference; calling a tool is an API. What MCP server cards, agent skills and WebMCP let an agent call, and where each one breaks.

- Source: /learn/agent-tools-not-pages
- Updated: 2026-10-12
- Reading time: 7 minutes
- Tags: MCP, agent skills, WebMCP, APIs

---
For an autonomous agent, a page it has to read is a worse interface than a tool it can call. Reading is inference: fetch the markup, parse it, guess which elements are actionable, hope the guess survives the next deploy. Calling is an API: a named operation, a typed input, a structured result, and a failure the caller can branch on. That distinction is the reason to publish a tool surface at all — and the right lens for judging the three surfaces below, which differ far more in maturity than their names suggest.

## Reading is inference; calling is an API

A page is a lossy encoding of an action: the visual affordance and the operation live in the same bytes. To act, the agent reconstructs intent from presentation — an anchor styled as a button, a form whose field names were written for humans, a JSON blob in a script tag. Each judgement is a place where a DOM refactor silently breaks a working integration.

A tool call moves that judgement to the server, once, at design time. Three consequences follow.

**Reliability.** The operation has a name and an input schema. The agent supplies conforming arguments or it does not; the server accepts them or returns an error saying why. Nothing depends on which CSS class the button had this week.

**Latency.** Reading a page costs a fetch, a parse, and at least one model round trip to decide what to do with the result. A tool call costs a fetch. Across five steps, that compounds.

**Error handling.** A page that fails is often still an HTTP 200 containing HTML. There is no exception to catch, only a layout that no longer matches expectations, so the agent retries or improvises. A tool call can return a typed failure — not found, invalid input, rejected, unloaded — and the caller can write a branch for each.

The claim is narrower than that: when the agent's job is to *do* something, a callable tool is better engineering than a page that merely implies one.

## MCP server cards: telling an agent the server exists

MCP is the one surface here with real deployment behind it: servers, clients and SDKs exist, and the specification is versioned. Its discovery model is narrower than "tool discovery" suggests.

A server card is a pre-connection document. [SEP-2127](https://modelcontextprotocol.io/seps/2127-mcp-server-cards) defines it as a self-contained JSON description of one MCP server: a reverse-DNS `name`, `title`, `description`, `version`, and the connection details needed before opening a transport — transport type, endpoint URL, supported protocol versions. It answers *does this service exist, and where do I connect?* The runtime counterpart is [`server/discover`](https://modelcontextprotocol.io/specification/2026-07-28/server/discover), which returns supported versions, capabilities and identity; list operations then enumerate what is callable.

That split is deliberate. The extension states that cards do not enumerate primitives — tools, resources and prompts remain subject to runtime listing — because a static manifest drifts from a running service. It also warns that a card is fetched before any connection, so its contents are unverified, and that clients must not treat them as authoritative for security decisions. The card advertises; the connection proves.

Where you host the file follows from that. Domain-level discovery runs through an AI catalog at `/.well-known/ai-catalog.json`, whose entries point at cards or inline them. The card may live anywhere the catalog points, with one reserved default: `GET <streamable-http-url>/server-card`. The extension explicitly does **not** recommend `/.well-known` for a card, since a card is application-level metadata while `.well-known` is for site-wide documents. In the wild you meet both: the MCP documentation site serves one at `/.well-known/mcp/server-card.json`, and it inlines a `tools` array the extension says cards should not carry — convenience, not contract. Our [MCP server card check](/checks/mcp-card) probes the `.well-known` locations first; serve both if you can.

## What a tool declaration contains

Whether it arrives from a live `tools/list` response or the object a page hands to a registration call, a tool declaration is small. This is one:

```json
{
  "name": "search_products",
  "title": "Search products",
  "description": "Search the public catalogue by free-text query and return matching products with prices.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": { "type": "string", "description": "Free-text search query" },
      "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
    },
    "required": ["query"],
    "additionalProperties": false
  }
}
```

`name` is the identifier used in every future call, so renaming it is a breaking change. `description` is the routing signal — what the model reads when deciding relevance, so a sentence about what the tool returns beats a label. `inputSchema` makes a call a call; without it the agent infers argument names from prose. Bounds and defaults turn a plausible call into a correct one.

## Agent skills: capability you can verify

Skills are not tools. A skill is a folder containing a `SKILL.md` file: YAML frontmatter with `name` and `description`, then markdown instructions, optionally with bundled scripts and references. The [Agent Skills specification](https://agentskills.io/specification) describes loading by progressive disclosure — names and descriptions at startup, full instructions only when a task matches, bundled resources only when referenced. A skill tells an agent *how*; a tool lets it act.

Discovery is the harder half for a remote agent, and where digests earn their place. An index at a well-known path lists each skill's name, description, artifact URL and digest. Name and description solve routing. The digest solves integrity: an agent that fetches a `SKILL.md` recomputes the hash and confirms it received the artifact the index described — which matters because skill content is instructions the agent will follow. Without a digest the index is pointers; with one it is verifiable pointers.

How much of that is guaranteed? The specification defines what goes inside a skill directory and explicitly does not mandate where skill directories live; remote discovery is left to the client — an API, a registry, or bundled assets. The well-known index with digests is a convention layered over a specified artifact format, which is why our [Agent Skills declaration check](/checks/agent-skills) grades a missing digest as a partial pass. Still the cheapest integrity win here.

## WebMCP: tools registered inside the page

WebMCP is materially less mature than the other two. The current [WebMCP draft](https://webmachinelearning.github.io/webmcp/) is a Draft Community Group Report from the W3C Web Machine Learning Community Group, and it says of itself that it is not a W3C Standard and not on the W3C Standards Track. It is an in-page JavaScript registration API being trialled in a browser, not a finished standard.

A page calls `document.modelContext.registerTool({ name, description, inputSchema, execute })`; the agent side lists registered tools and executes one by name. The draft adds lifecycle events (`toolactivated`, `toolcancel`, `toolchange`), an `AbortSignal` for calls interrupted by page unload, annotation hints (`readOnlyHint`, `untrustedContentHint`, `consequentialHint`), and origin scoping under a `tools` permissions policy. Its framing is that such a page is an MCP server whose tools run in client-side script.

The constraint to internalise is this: **WebMCP does not define a well-known manifest file.** There is no document to fetch, no index to crawl, no card at a path. Tools exist only after the page loads and script registers them, so discovery costs a page load and JavaScript execution, and the tool surface belongs to a document rather than a site. Any tooling that claims to find WebMCP tools without running the page is doing something else.

What the draft gets right is error reporting. Platform-defined failures map to exception names a caller can act on: `NotFoundError` when the tool is not registered, `DataError` when arguments will not parse, `OperationError` when the callback rejects or the result will not serialise, `AbortError` when the document unloads mid-call. Our [WebMCP browser tool declaration check](/checks/webmcp) scores tools without an `inputSchema`, and pages that load the API without registering anything, as partial passes — both leave the agent inferring.

## A card is not a contract

Tool declarations and OpenAPI are complementary, and conflating them is the most common modelling mistake here. A contract describes the shape of a call: paths, methods, parameters, request and response schemas, status codes. [OpenAPI](https://spec.openapis.org/oas/latest.html) is the established way to write that down, and the right place for detail. A tool declaration does what the contract cannot: it tells an agent the call exists at all.

Both are needed, in that order. A declaration without a contract names an operation whose parameters the agent must guess. A contract without a declaration is detail the agent cannot find. What closes the gap is a stable entry point — an [API catalog](/checks/api-catalog) such as RFC 9727 defines, pointing from a well-known location at the API description document, so a client follows a link instead of discovering by search. Our [OpenAPI definition check](/checks/openapi) measures whether that document is genuine OpenAPI 3.x with a non-empty `paths` object, because an empty or human-readable document is a contract in name only. The same layering appears in agent-to-agent work: the [A2A specification](https://a2a-protocol.org/latest/specification/) defines Agent Cards at `/.well-known/agent-card.json`.

## The failure modes worth designing against

When a tool surface lies, the agent does not get an error it can reason about — it gets a plausible failure it will retry. Three cases recur.

**A server card for an endpoint that does not exist.** The card is fetched before any connection, so nothing validates it. The agent configures a transport, fails, retries, and may surface a timeout to a user. Cards are cheap to generate and therefore cheap to forget: generate them from the running service in CI and fail the build when they disagree.

**Tools declared with no parameter schema.** A tool with a name, a description and nothing else is a page in disguise. The agent infers argument names, types and formats from prose — exactly the inference the tool surface was meant to remove. Declare types, mark required fields, bound the values.

**A contract that is already stale.** Renaming an operation or dropping a field breaks agents that cached your contract; the error lands as a 404 or 400 at call time, far from the change that caused it. Additive change with a deprecation window is cheaper than the support thread.

The compound version: a card, a contract and a page each describing a different vintage of the API. Pick one source of truth — the running service — generate the others from it, and publish a version or digest.

## What to publish, in order

Start with what you actually serve. If an operation is safe to expose, declare it with a name, a description and an input schema; where the service is an MCP server, publish a card that resolves and agrees with what the server reports at runtime. Make the contract findable: an OpenAPI document plus a catalog entry pointing at it. Publish digests for anything an agent fetches. Keep the page for humans, and stop asking the agent to read it when you could have given it a tool.

The discipline is the same as any other API: do not advertise an endpoint you do not serve, and do not make a caller guess. Our [MCP server card](/checks/mcp-card), [Agent Skills declaration](/checks/agent-skills), [WebMCP browser tool declaration](/checks/webmcp), [OpenAPI definition](/checks/openapi) and [API catalog](/checks/api-catalog) checks measure those five surfaces separately, and the [methodology](/methodology) explains how the rubric weights them and what a partial pass means. Every scan record names the rubric version that scored it.
