AI Engineer Roadmap All lessons Interactive Quiz MernStackDev GitHub

MCP Tools

Play the interactive lesson: MCP toolsRead on GitHub
On this page
  1. Explain it like I'm new
  2. Watch it
  3. How it works
  4. Code example
  5. When to use it / when not to
  6. Common mistakes
  7. Recap
  8. Interview questions
  9. Go deeper

Level: Intermediate · Reading time: 12 min · Phase: 6 — MCP

In one line: Tools are actions a server exposes, each with a name, a description and a JSON Schema for its input, and the model decides when to call them.

Before you start: MCP client and Tool calling. You should know that the model asks the app to run a function.

Explain it like I'm new

A tool is a button on a remote control. The label (description) tells the model what the button does. The model presses it by sending a name and arguments. A tool is model-controlled: the LLM chooses to use it, usually with the user's approval.

JSON Schema is a standard way to describe the shape of JSON: "an object with a number a and a number b, both required". The server publishes one per tool so the model knows what to send.

Words to know:

Watch it

MCP tools: Actions the model can choose to call.
MCP tools: Actions the model can choose to call.

It plays on a loop. Read the caption in the box, one step at a time. Want to go at your own pace? Step through it with Previous and Next.

How it works

  1. Client sends tools/list.
  2. Server replies with tools: name, description, inputSchema (and optional title, annotations).
  3. The model picks a tool. Client sends tools/call with name and arguments.
  4. Server validates arguments, runs the tool and replies with content (a list of items, e.g. {"type":"text","text":"42"}) and isError.

Request and reply (shapes from the spec; the mini toy produces them):

{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"add_numbers","arguments":{"a":2,"b":40}}}

{"jsonrpc":"2.0","id":2,
 "result":{"content":[{"type":"text","text":"42"}],"isError":false}}

Two kinds of failure. An unknown tool or bad arguments is a protocol error: a JSON-RPC error object (code -32602, "invalid params"). A tool that runs but fails (a file is missing) should return a normal result with isError: true, so the model can read the message and recover. Check the tools page of the spec for current guidance.

Annotations are hints such as readOnlyHint. They help clients decide how to ask for approval. Treat them as hints, not guarantees, from servers you do not trust.

sequenceDiagram
  participant M as Model
  participant H as Host/Client
  participant S as Server
  H->>S: tools/list
  S-->>H: tools + schemas
  M->>H: call add_numbers(2, 40)
  H->>S: tools/call
  S-->>H: content "42", isError false
  H->>M: result text

Worked example

Three calls to the same toy server, and what each one returns.

  1. tools/call with add_numbers, a = 2, b = 40. Result: content holds the text 42 and isError is false.
  2. tools/call with a tool called delete_everything. The server has no such tool, so it replies with a JSON-RPC error (code -32602). That is a protocol error.
  3. A tool that exists but cannot do its job, for example reading a missing file, should reply with a normal result whose isError is true and a message the model can read. The model can then try something else.

The difference matters: a protocol error says "your request was wrong", while a tool error says "the request was fine, the work failed".

Code example

From code/hello-mcp-server/src/index.ts (real, builds in CI):

server.registerTool(
  "word_count",
  {
    title: "Word count",
    description: "Count the words in a piece of text.",
    inputSchema: { text: z.string().min(1) },
    annotations: { readOnlyHint: true },
  },
  async ({ text }) => ({ content: [{ type: "text", text: String(text.trim().split(/\s+/).length) }] })
);

zod turns inputSchema into JSON Schema and validates input before your function runs. An empty string is rejected.

Python toy version, with the same two failure types (see code/mcp_mini_jsonrpc.py):

try:
    client.call_tool("nope", {})           # protocol error -> RpcError(-32602)
except Exception as e:
    print(e)
print(client.call_tool("add_numbers", {"a": 1, "b": "x"})["isError"])  # True

When to use it / when not to

Common mistakes

Recap

If you remember only three things:

Interview questions

  1. Protocol error vs isError?

<details><summary>Answer</summary>Protocol errors (unknown tool, bad params) use the JSON-RPC error field. Tool execution failures return a normal result with isError: true so the model can adapt.</details>

  1. Why are tools "model-controlled"?

<details><summary>Answer</summary>The LLM decides when to call them based on descriptions. Hosts should still ask the user to approve.</details>

  1. What is the risk in tool output?

<details><summary>Answer</summary>It is untrusted text and may carry prompt injection. Do not let it grant itself more permissions.</details>

Go deeper

Prev: 03-mcp-client.md · Next: 05-mcp-resources.md · Back to phase: Phase 6