Agents & MCPDeveloper skill

MCP Server Builder

Guides building a quality MCP server for an external API: task-shaped tool design, auth, error handling, and testing.

What it does

You want an AI assistant to work inside a service you already use, such as your ticketing system, your database, or an internal API. The connector that makes this possible is called an MCP server, and building one is easy to get half right: the assistant can technically reach the service but keeps calling the wrong thing, asking for data it cannot use, or stopping at an error message that tells it nothing.

With this skill active, the work starts from the jobs the assistant should be able to finish rather than from a list of API routes. Each tool gets a name and a description written for the assistant that will choose it, typed inputs so a wrong value comes back as a correction instead of a failure, results trimmed to what is actually useful, and error messages that say what to do next. Credentials stay in the environment, and anything that changes data is kept separate from anything that only reads.

You end up with a working server, a short review pass over its tool list, and a set of real task questions used to check that an assistant can actually complete work with it.

You end up with a working MCP server, a reviewed tool list, and task questions that prove an agent can use it.

When to reach for it

Use it when you are connecting a service to an AI assistant for the first time, or when a connector already exists and the assistant keeps using it wrong, which is nearly always a design problem rather than a model problem.

Skip it if someone already publishes a maintained connector for that service. Installing theirs is faster than building your own, and this skill is for the case where nothing suitable exists.

What's inside

mcp-server-builder/
├── SKILL.md
├── references/
│   ├── examples.md
│   └── reference.md
└── scripts/
    └── lint_tools.py
SKILL.md
---
name: mcp-server-builder
description: Builds an MCP server that exposes an external API or system as tools an agent can call correctly on the first try - tool design and naming, typed schemas, model-facing descriptions, pagination, errors that guide recovery, auth, and a test pass. Use when wrapping a service as MCP tools, or when reviewing a server whose tools an agent keeps misusing.
license: Apache-2.0
metadata:
  source: anthropics/skills
---

# MCP Server Builder

You build the adapter between an agent and an outside system. MCP (Model Context Protocol) is the open standard that lets a client discover your tools and call them over one interface, so the same server works in any MCP-capable client.

The measure of quality is not coverage or elegance. It is whether an agent that has never seen your server accomplishes a real task with it, first try, without reading your source.

## Workflow

1. **Collect the jobs, not just the endpoints.** Ask what an agent should actually accomplish with this service. Write those down as sentences ("find the open tickets for a customer and add a note"). They drive every later decision.
2. **Read the API.** Endpoints, auth model, rate limits, pagination style, error shapes, and which operations are destructive. Note anything that needs an id the agent has no way to obtain.
3. **Pick language and transport.** stdio for a local server that runs as a subprocess of the client; streamable HTTP for a remote one serving multiple clients. TypeScript and Python both have first-party SDKs; follow whichever the project already uses. See `references/reference.md` for the transport comparison.
4. **Design the tool set.** Cover the service's common operations, and add a task-shaped tool wherever a job takes three predictable calls in a row. Details and the naming convention are below.
5. **Type every input.** Zod in TypeScript, Pydantic in Python. Constraints, enums, and a description on each field, with an example value where the format is not obvious.
6. **Write each description for the model.** Name and description are the only things an agent sees when choosing. State what the tool does, when to use it, and what comes back.
7. **Shape the output.** Return the fields an agent reasons over, plus the identifiers it needs for follow-up calls. Paginate lists. Declare an output schema where the SDK supports one.
8. **Turn errors into guidance.** Every failure returns text that names the problem and the next move.
9. **Build one tool end to end before the rest.** Auth, call, shape, error path, all working, then repeat the pattern.
10. **Test from a real client.** Discovery, a happy path, a bad input, an auth failure. Then write about ten realistic task questions and check an agent can answer them using only the tools.

```text
Server review checklist
[ ] Every tool name is prefixed with the service and starts with a verb
[ ] Every description says what it does, when to use it, and what it returns
[ ] Every input field is typed, constrained, and described
[ ] List tools take a limit and return has_more plus a cursor or next offset
[ ] Outputs carry the ids needed for the follow-up call
[ ] Every error path returns a sentence an agent can act on
[ ] Destructive tools are annotated and separated from read tools
[ ] Credentials come from the environment and never appear in output or logs
[ ] A real client completed one full task without reading the source
```

## Tool design

**Name them predictably.** `{service}_{action}_{resource}`, lowercase with underscores: `linear_create_issue`, `linear_list_projects`. The service prefix matters because your server will sit alongside others in the same agent, and a bare `search` collides. Start with a verb, and be specific enough that the name alone narrows the choice.

**Match tool shape to the work.** Two failure modes sit either side of the right answer:

- One tool per endpoint, so the agent has to orchestrate five calls the server could have done itself.
- Only a handful of workflow tools, so anything the author did not anticipate is impossible.

Cover the common operations, then add workflow tools for the sequences that recur. When you are unsure, lean toward coverage: an agent can compose small tools, but it cannot invent one that does not exist.

**Annotate behavior.** `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`. These are hints for clients, not a security boundary, so still enforce anything that matters in the code.

**Paginate every list.** Respect a `limit`, default it to something modest, and return `has_more` with a cursor or next offset. A tool that dumps ten thousand rows into a context window has failed even though it returned the right data.

## Descriptions are prompts

An agent picks a tool from its name and description alone. Write them the way you would write instructions to a competent stranger who cannot see your code.

```python
@mcp.tool()
def zendesk_search_tickets(
    query: str,
    status: Literal["open", "pending", "solved"] | None = None,
    limit: int = 25,
) -> dict:
    """Search support tickets by text, optionally filtered by status.

    Use to find tickets when you do not already have a ticket id. Returns the id,
    subject, status, requester email, and last update time for each match, plus
    has_more and next_cursor. Use zendesk_get_ticket for the full conversation.
    """
```

The last sentence is doing real work: it tells the agent where to go next, which is how you keep a search tool from returning entire conversations nobody asked for.

## Errors that guide recovery

A failure is a chance to tell the agent what to do differently. Return the problem and the fix, inside the result rather than as a protocol crash.

```typescript
try {
  const result = await api.listOrders(args);
  return { content: [{ type: "text", text: format(result) }] };
} catch (error) {
  return {
    isError: true,
    content: [{
      type: "text",
      text: `Could not list orders: ${describe(error)}. ` +
            `If this was a rate limit, wait 30s and retry with a smaller limit.`,
    }],
  };
}
```

Compare what the agent can do with each of these:

| Returned | What happens next |
| --- | --- |
| `500` | The agent guesses, or gives up |
| `Error: invalid request` | The agent retries the same call |
| `status must be one of open, pending, solved` | The agent fixes the argument and succeeds |

Never let an error carry a stack trace, a query string with credentials in it, or internal hostnames.

## Auth and safety

- Read credentials from environment variables or secure config. Validate them at startup and fail with a clear message rather than on the first tool call.
- Scope tokens to the minimum the tools need. For per-user auth, keep each session's token isolated.
- Validate inputs before they reach the API: path segments, urls, sizes, ranges. A tool that takes a path and reads a file is a directory-traversal bug waiting to happen.
- Keep read and write in separate tools. A lookup that can mutate data will eventually mutate data.
- A local HTTP server binds to `127.0.0.1` and checks the `Origin` header.
- Log tool calls for debugging, with argument values redacted.

## Example

**Request.** "Wrap our ticketing API so the support agent can work in it."

**Weak.** Three tools: `get(path)`, `post(path, body)`, `put(path, body)`. This is the HTTP client with extra steps. The agent has to know the routes, so it guesses, and every mistake is a 404 with no recovery path.

**Strong.** Five tools, named and shaped around the work:

```text
tickets_search_tickets(query, status?, assignee?, limit=25)  read
tickets_get_ticket(ticket_id)                                read
tickets_add_comment(ticket_id, body, public=false)           write
tickets_set_status(ticket_id, status)                        write
tickets_list_queues()                                        read
```

`search` returns ids plus enough context to choose between results, and says in its description to call `get_ticket` for the full thread. `set_status` takes an enum, so a wrong value comes back as `status must be one of open, pending, solved` and the agent self-corrects instead of failing. Writes are separate tools with `readOnlyHint: false`, so a client can require confirmation on exactly those two.

## Edge cases

- **The API is enormous.** Cover the operations that appear in the jobs list, then stop. Every extra tool costs context in every session and adds a way to choose wrong. Growth is cheap later; a bloated first release is not.
- **An operation needs an id the agent cannot get.** That is a missing tool. Add the lookup, or accept a human-meaningful identifier and resolve it inside the server.
- **The API paginates in its own way.** Hide it. The agent sees your `limit` and cursor, not their `page[offset]` scheme.
- **A response is huge.** Summarize or paginate at the server. Returning forty fields per row so the agent can ignore thirty-seven of them wastes the window you are trying to protect.
- **Long-running operations.** Return a job id immediately with a tool to check status, rather than holding the call open until it times out.
- **The server exists and an agent keeps misusing one tool.** Read the description as if you had never seen the code. Nearly always the name is ambiguous, the return shape is undocumented, or two tools sound like the same thing.
- **Changing a tool other agents already use.** Add the new shape alongside the old one and deprecate in the description. A silent shape change breaks every caller at once.

## Files

- `references/reference.md` - naming conventions, the transport comparison, the pagination contract, annotation table, response format guidance, and the security checklist. Read during design, and again before the final review.
- `references/examples.md` - two full builds: an endpoint-mirroring server rewritten into a task-shaped one, and a read-only search server with its schemas, error messages, and evaluation questions. Read before designing your own tool set.
- `scripts/lint_tools.py` - run it against a JSON dump of your server's tool list: `python scripts/lint_tools.py tools.json`. Flags naming inconsistencies, thin descriptions, undescribed parameters, missing pagination, and unannotated destructive tools. It reads the declared shape only, so it catches design slips, not runtime bugs.

## Attribution

Adapted from the `mcp-builder` skill in [anthropics/skills](https://github.com/anthropics/skills/tree/main/skills/mcp-builder), Apache-2.0. The four-phase workflow, naming conventions, pagination and annotation guidance, transport selection and evaluation approach come from there. The lint script, the error-recovery table and the worked builds are ours.

Install it

Claude Code (this project)

Unzip the download into .claude/skills/. The folder is already named right.

Claude Code (every project)

Unzip into ~/.claude/skills/ to use it everywhere.

claude.ai

Upload the zip under Settings, then Capabilities, then Skills (paid plans).

How to call it

Just describe the task. The description line inside the skill does the matching. Or call it directly:

/mcp-server-builder build an MCP server for this APIconnect our internal tool to Claudethe agent keeps calling my tool wrongreview my MCP tool designwrap this service as tools an agent can use

Works beyond Claude

Skills use the open Agent Skills format. The exact same folder works in OpenAI Codex, Gemini CLI, Cursor, GitHub Copilot and other tools that read the format.

Using a plain chat app? Paste SKILL.md into ChatGPT or Gemini as instructions. You lose auto-triggering, but the method still works.

agentskills.io

Pattern credit: anthropics/skills — mcp-builder

Related skills