---
title: "MCP Plugin"
description: "Discover external tools, register their server routes, and follow a call through MCP. Compare an unavailable tool with a failed lookup."
source: https://vizlearn.app/ai/agents/mcp-plugin
access: premium (interactive player requires Lifetime; this doc is free)
---

# MCP Plugin

An MCP plugin lets an AI application use tools implemented by another program. The application's host records each tool's description and execution address. Its dispatcher can then run a built-in tool locally or send an external call through an MCP client, while the external server keeps the tool implementation.

Suppose you need to read a project file and its retry-policy note. The host
already has `read_file`, but the note belongs to the Notes server. Adding that
server's tools gives the same dispatcher another destination. Think of the
tool pool as a directory with forwarding addresses: a description says what
you can request; its address tells the host where to send it. A correct address
still cannot make a missing note exist.

## How an MCP Plugin Extends the Tool Pool

The host is the application coordinating the model and tools. It owns the tool
pool and an MCP client for the Notes server. The server implements the external
tools; it can run in a local process or as a remote service. Crossing the
boundary in this drawing means leaving the host's executor, not necessarily
crossing the internet. These roles follow the MCP
[architecture](https://modelcontextprotocol.io/specification/2026-07-28/architecture).

"Plugin" here is the host's packaging for integration configuration. MCP does
not require every application to use that package format. Enabling the fixture
integration tells this host to create its Notes client and discover tools.

## MCP Plugin Step by Step

The default enables the Notes integration and requests note `n1`. Both calls
are authored test inputs so you can inspect routing without guessing what a
model might choose.

**1. Start with the built-in tool.** The host pool contains `read_file`, with a
route to its local executor. Both execution counters are zero. The integration
creates a Notes client inside the host; creation alone sends no tool call.

**2. Discover the capability, then list the tools.** The host sends
`server/discover` with request ID `notes-1`. The response advertises a tools
capability. Next, `tools/list`, ID `notes-2`, returns `read_note`, which accepts
a string `id`, and `search_notes`, which accepts a string `query`. At this
point the pool still contains one registered entry. The two returned
descriptions are data awaiting registration.

**3. Register destinations.** This host prefixes the external names with
`notes.` and retains the original server names in its routes:

| Exposed name | Runs through | Name sent to server |
|---|---|---|
| `read_file` | Local executor | — |
| `notes.read_note` | Notes client | `read_note` |
| `notes.search_notes` | Notes client | `search_notes` |

The pool now has three entries. This prefix is a host naming choice. The
server receives its own tool name when a call is dispatched. For model tool
selection, the host offers names, descriptions, and input schemas; its
executor objects stay inside the host.

**4. Run the local call.** Authored call `c1` requests
`read_file({"path":"README.md"})`. The pool selects the local executor, which
reads the fixture file and returns `Project: Sketchpad`. The host attaches this
observation to `c1`. One local tool has executed; no external tool has run.

**5. Route the external call.** Call `c2` requests
`notes.read_note({"id":"n1"})`. Its registered route selects the Notes client.
The client sends `tools/call` with request ID `notes-3`, server name
`read_note`, and the unchanged argument `{"id":"n1"}`.

**6. Execute and return.** The Notes server looks up `n1` in its own fixture
collection. It returns `Retry policy: Retry temporary failures up to three
times.` The complete result has `isError: false`, and its response ID is
`notes-3`. The host correlates that response with its pending call `c2` and
attaches the observation. The final counters are **one local execution and one
external execution**.

The wire request ID and the originating call ID belong to different layers.
The host maps between them. The MCP
[base protocol](https://modelcontextprotocol.io/specification/2026-07-28/basic/index)
defines request/response correlation; the fixture's `c1` and `c2` identifiers
are host bookkeeping. No final model answer is generated in this walkthrough.

## Why a Listed Tool Can Still Fail

Keep the integration enabled and change the note ID to `missing`. Discovery
and registration produce exactly the same three-entry pool. The host finds
the route, the client sends the request, and the server runs `read_note` once.
The lookup now returns `Note not found: missing` with `isError: true`.

Availability answered whether the host could dispatch this tool. The lookup
answered whether the requested data existed. The successful discovery cannot
answer the second question in advance. In MCP, tool execution failures travel
inside a tool result; protocol errors, such as an unknown server tool name,
use a JSON-RPC error response. The
[tools specification](https://modelcontextprotocol.io/specification/2026-07-28/server/tools)
defines the distinction.

Now disable the integration while retaining either note ID. The pool contains
only `read_file`. Call `c1` still succeeds. The authored `c2` probe cannot find
`notes.read_note`, so the host produces a local availability error without
sending any MCP traffic. It would be misleading to label that error a response
from a server the host never contacted.

Try the **Integration disabled** and **Listed tool, missing note** scenarios.
Each pauses before `c2` is routed. Predict whether the server's execution
counter will increase, then advance the player to check.

## Check the Failure Location Before Changing the Input

A tool integration failure and a failed lookup need different repairs. Use the
registry entry, outbound call and returned failure flag to locate the problem:

| What this run shows | Where it failed | What to check next |
|---|---|---|
| No `notes.read_note` entry; no `tools/call` | Host availability lookup | Enable the integration and check tool registration. Changing `n1` cannot create a route. |
| The route exists; `read_note` returns `isError: true` | Notes tool execution | Check the note ID and the server's data. Repeating discovery returns the same tool description. |

For another tool on the same Notes server, the host needs another description
and server route in its directory. The existing client can carry that tool's
calls. The dispatcher still looks up the requested name and follows the
registered destination. This is the part that transfers beyond the two calls:
adding a tool extends the registry without requiring a new dispatch procedure
for every tool name.

## Edge Cases

- **No integration:** the pool retains the built-in tool. An argument change
  cannot supply the missing external route.
- **A missing note:** the route exists and the tool executes, but its result
  reports failure. A delivered error is not a dropped packet.
- **No search matches:** the advertised `search_notes` implementation returns
  an empty-search message without an execution error. This tool is included in
  discovery, although the two-call demo invokes only `read_note`.
- **Tool discovery and permission:** a discovered tool still needs to satisfy
  the host's policy before use. The read-only fixture policy permits these
  calls; [Permission](https://vizlearn.app/ai/agents/permission) opens that separate decision.

## Common Mistakes

- **Treating a descriptor as executable code.** The host needs an implementation
  or a route behind the name. A schema validates a call shape; it cannot read a
  note.
- **Sending the host alias unchanged.** Here `notes.read_note` identifies a
  host entry, while the Notes server expects `read_note`.
- **Counting discovery as tool execution.** Capability discovery and listing
  leave both tool execution counters unchanged.
- **Dropping the error flag.** The next consumer needs the explicit failure
  signal even when the result contains readable text.
- **Assuming the model chose an unavailable tool.** The disabled scenario
  deliberately injects an authored availability probe. It makes no claim about
  how a real model would behave with that exposed tool list.

## Protocol Revision Used by This MCP Plugin

This run uses **MCP 2026-07-28**. Requests carry their protocol version and
client metadata. The host chooses to start with `server/discover`, which asks
for supported versions and capabilities. Calling it is optional for clients;
it is not a mandatory session handshake. Earlier protocol revisions used
`initialize` followed by `notifications/initialized`. See the
[discovery contract](https://modelcontextprotocol.io/specification/2026-07-28/server/discover)
and [versioning explanation](https://modelcontextprotocol.io/docs/2026-07-28/learn/versioning)
when comparing examples from different revisions.

## A Note on Simplification

This is an explainer with an in-memory host, one simulated server, two fixed
calls, and a small fixture dataset. It is not a substitute for the protocol
specification or formal training. Transport, authentication, permission
negotiation, model decisions, streaming, retries, pagination, caching,
subscriptions, resources, and prompts are omitted. The host-specific plugin
package and alias convention are teaching choices. The inspector shows payload
excerpts; the generated records retain required request metadata and response
fields. Playback seconds are teaching time, not measurements of MCP latency.
