# Wrap your own API as an MCP tool

To give Claude direct access to one of your own API endpoints, you build a small
MCP server that exposes that endpoint as a single tool, define the tool's input
schema in JSON, and register the server with the Claude Code CLI. Once it is
connected, Claude calls your endpoint on its own when a task needs it, instead of
you pasting data back and forth.

This guide shows the shape of that tool, how to register it, and how to test the
loop. If you are new to the protocol, read [MCP fundamentals](/guides/mcp-fundamentals)
first for the mental model of hosts, clients, servers, and primitives.

## Start with one endpoint, not your whole API

The instinct is to expose every route. Resist it. A single, well-described tool
that maps to one endpoint is easier for Claude to use correctly and easier for
you to reason about.

Pick one endpoint with a clear job. Good first candidates:

- A read that returns one record by id, like `GET /orders/{id}`.
- A search that takes a few filters, like `GET /tickets?status=open`.
- A status check, like `GET /deploys/latest`.

Start with a read-only endpoint. A tool that only reads data cannot damage
anything when Claude calls it while you are still learning the loop. You add
write tools later, once the read path works.Claude decides on its own when to fire a tool, so a wrapped write endpoint (delete, refund, send) can run a destructive action without you confirming it. When you do add one, make the API idempotent or require an explicit confirmation argument, never just point the tool at a raw `DELETE`.

## What is an MCP tool?

An MCP tool
is a named function the model can call. The server advertises it in a `tools/list`
response, and each tool definition carries four things that matter to you:

- **`name`** · a unique identifier the model uses to call the tool, like `get_order`.
- **`title`** · an optional human-readable label for display.
- **`description`** · plain text that tells the model what the tool does and when
  to use it. This is the single most important field for getting good calls.
- **`inputSchema`** · a JSON Schema
  object that defines the arguments the tool accepts.

Here is the shape of one tool definition, taken from the protocol's own example
format. This wraps a read endpoint that fetches one order by id:

```json
{
  "name": "get_order",
  "title": "Order Lookup",
  "description": "Fetch one order by its id. Returns status, line items, and total. Use when the user asks about a specific order.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string",
        "description": "The order id, like ORD-4821"
      }
    },
    "required": ["order_id"]
  }
}
```

The model reads `description` to decide whether the tool fits the task, then
reads `inputSchema` to build valid arguments. Both are part of your contract with
the model, so write them as carefully as you would write a function signature.
For the mechanics of how a model decides to call a tool, see [tool calling explained](/guides/tool-calling-explained).

## How does an MCP tool call flow?

When Claude decides your tool fits a task, the host routes the call to your
server, your server hits your API, and the result flows back as context.

Your server is the thin layer in the middle. It receives the tool call, maps the
arguments onto an HTTP request to your API, and returns the response as a tool
result. You do not write any code that calls the model. Claude is the caller.

## How do I write a strong input schema?

The input schema is where most tool quality comes from. A vague schema produces
vague calls. Treat each property like a typed parameter with documentation.

Three rules cover the common cases:

1. **Type every property.** Use `string`, `number`, `integer`, `boolean`,
   `array`, or `object`. This stops Claude from passing a number where you expect
   a string.
2. **Describe every property.** The `description` on a property tells Claude what
   the value means and the format you expect, like `"ISO 8601 date, like 2026-06-13"`.
3. **List what is required.** The `required` array names the properties the call
   cannot omit. Leave optional filters out of it.

Here is a richer schema for a search endpoint that takes a status filter and a
result limit:

```json
{
  "name": "search_tickets",
  "title": "Ticket Search",
  "description": "Search support tickets by status. Returns a list of matching tickets, newest first.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "status": {
        "type": "string",
        "enum": ["open", "pending", "closed"],
        "description": "Ticket status to filter by"
      },
      "limit": {
        "type": "integer",
        "description": "Maximum number of tickets to return. Defaults to 20 on the server."
      }
    },
    "required": ["status"]
  }
}
```

The `enum` constraint matters. It tells Claude the only valid values for
`status`, so it will not invent a fourth state your API rejects. Use `enum`
whenever an argument has a fixed set of options.

## Scaffold the server with Claude

You do not have to write the server boilerplate by hand. Anthropic ships an
official plugin that scaffolds an MCP server for you. (To understand the pieces
it generates, the [build an MCP server](/guides/build-an-mcp-server) guide builds one from scratch.) Inside a Claude Code
session, install it:

```bash
/plugin install mcp-server-dev@claude-plugins-official
```

If Claude Code reports that the marketplace is not found, add it first, then
retry the install:

```bash
/plugin marketplace add anthropics/claude-plugins-official
```

Once it is installed, run the build skill. Claude asks about your use case and
scaffolds a local stdio server or a remote HTTP server:

```bash
/mcp-server-dev:build-mcp-server
```

Give Claude the specifics it needs to generate a correct wrapper:

```text
Build a local stdio MCP server that exposes one tool named get_order.
It wraps GET https://api.example.com/orders/{order_id} on my service.
The tool takes one required string argument order_id and returns the
JSON response as text. Read the API base URL from an environment
variable API_BASE_URL and the key from API_KEY. Do not add any other
tools yet.
```

The more precise you are about the endpoint, the arguments, and the auth, the
closer the generated server is to working on the first run.

## Register the server with the CLI

A server is useless until a host connects to it. You register it with
`claude mcp add`. The transport you choose depends on where the server runs.

For a local server that runs as a stdio
subprocess on your machine, pass the command after a `--` separator. Everything
after `--` is handed to the server untouched, so Claude does not try to parse the
server's own flags:

```bash
claude mcp add --transport stdio --env API_KEY=YOUR_KEY orders \
  -- node ./servers/orders-server.js
```

For a server you host behind a URL, use the HTTP transport. This is the
recommended transport for remote servers, and it supports tokens and OAuth:

```bash
claude mcp add --transport http orders https://mcp.example.com/mcp \
  --header "Authorization: Bearer YOUR_TOKEN"
```

The `--env` flag accepts multiple `KEY=value` pairs and passes them into the
server's environment. Keep at least one other option between `--env` and the
server name, since the CLI reads the next token after `--env` as another pair.

## Where should the MCP server config live?

The `--scope` flag controls where Claude Code stores the server entry and who can
see it. There are three scopes:

| Scope | Loads in | Shared with team | Stored in |
| --- | --- | --- | --- |
| `local` | Current project only | No | `~/.claude.json` |
| `project` | Current project only | Yes, via version control | `.mcp.json` in project root |
| `user` | All your projects | No | `~/.claude.json` |

`local` is the default and keeps the server private to you in one project. Use
`project` when you want the whole team to get the same tool, since the
`.mcp.json` file is meant to be checked into version control:

```bash
claude mcp add --transport http --scope project orders https://mcp.example.com/mcp
```

A project-scoped `.mcp.json` follows a standard shape. For a remote endpoint with
auth pulled from an environment variable, it looks like this:

```json
{
  "mcpServers": {
    "orders": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${API_TOKEN}"
      }
    }
  }
}
```

Claude Code expands `${API_TOKEN}` from your environment, so the secret stays out
of the file. You can also write `${API_TOKEN:-default}` to supply a fallback when
the variable is unset.Because `.mcp.json` is meant to be committed, pasting a raw key straight into its `headers` (or running `claude mcp add --scope project` with a literal `Bearer YOUR_TOKEN`) ships your credential to every clone and into git history forever. Always reference the secret as `${API_TOKEN}` and keep the real value in your environment, so the project file holds only the variable name.

## How do I test that the tool works?

Registration is only half the job. Confirm the server connects and the tool
shows up.

List your servers and check the status:

```bash
claude mcp list
```

Get the details for one server to confirm the transport and config are right:

```bash
claude mcp get orders
```

Inside an interactive session, the `/mcp` panel shows the tool count next to each
connected server and flags a server that advertises tools but exposes none. A
project-scoped server from `.mcp.json` shows as pending approval until you
approve it in an interactive run.

Then drive a real call with a prompt that needs the tool. Watch which tool
Claude picks and what arguments it builds:

What is the status of order ORD-4821?

If Claude calls `get_order` with `{"order_id": "ORD-4821"}` and you get the
record back, the loop works end to end.

## When Claude misuses the tool, fix the description

If Claude calls the wrong tool, skips a required argument, or passes a bad value,
the problem is almost always the description or the schema, not the model.

Tighten the contract and reconnect:

- **Wrong tool chosen** · the `description` is ambiguous. State plainly what the
  tool does and when to use it.
- **Missing argument** · add the property to the `required` array.
- **Invalid value** · add a `type`, an `enum`, or a format note in the property
  `description`.

A short, specific description beats a long, vague one. Claude Code truncates a
tool description at 2KB, so put the critical detail near the start.

## Return results Claude can read

Your server returns tool results as content. The common case is a text block that
carries the JSON your API returned:

```json
{
  "content": [
    { "type": "text", "text": "{\"order_id\":\"ORD-4821\",\"status\":\"shipped\"}" }
  ],
  "isError": false
}
```

When your API call fails, report it as a tool execution error so Claude can react
instead of treating the failure as data. Set `isError` to true and put a plain
message in the text:

```json
{
  "content": [
    { "type": "text", "text": "Order lookup failed: 404 Not Found for ORD-9999" }
  ],
  "isError": true
}
```

With `isError` set, Claude sees the call failed and can retry with a different
argument or tell the user what went wrong, rather than passing a 404 body forward
as if it were a real order.

## Keep large results from flooding context

If your endpoint can return a large payload, like a full schema or a long list,
mind the output limits. Claude Code warns when a tool's output exceeds 10,000
tokens and caps output at 25,000 tokens by default. Raise that ceiling for a
session when you need it:

```bash
export MAX_MCP_OUTPUT_TOKENS=50000
claude
```

A better fix is to make the tool return less. Add a `limit` argument to the
schema, paginate, or return only the fields the task needs. A focused result is
cheaper and easier for Claude to use than a giant blob.

## A safe path to the first working tool

The shortest route to a tool you trust:

1. Pick one read-only endpoint.
2. Write the `name`, `description`, and `inputSchema` by hand so the contract is
   exactly what you mean.
3. Scaffold the server with the `mcp-server-dev` plugin, feeding it that contract.
4. Register it at `local` scope with `claude mcp add` so the experiment stays
   private.
5. Confirm it connects with `claude mcp list` and `/mcp`.
6. Drive a real prompt and check the tool name and arguments Claude builds.

Once the read path is solid and the input schema produces clean calls, you can
add a second tool, widen the scope to `project` for your team, or wrap a write
endpoint with the same discipline. The [MCP configuration guide](/guides/mcp-configuration)
covers scopes and transports in more depth.
