DEV Community

Magnus Williams
Magnus Williams

Posted on

Your MCP Tools Are an API for a Very Literal Intern

Picture a tool called save that takes a content string. You ask an agent to save a 12-page document. It saves a 40 word summary, cheerfully reports success and moves on. Technically correct. Completely useless.

That's rarely the model's fault. It's usually the tool's. An agent reads your tool definitions the way a very literal intern reads a sticky note on their first day. It has no hallway context, no memory of last week's standup and no idea what you were thinking when you named something save.

Here are the rules we follow when designing tools that agents use well. None of them are clever. All of them save time.

1. Name tools as noun_verb, and be boring about it

Agents choose tools mostly by name and description. A vague name invites guessing.

We settled on a noun_verb pattern across the whole server:

goals_list
goals_add
goals_complete
documents_list
document_read
document_save
reminder_add
reminders_list
Enter fullscreen mode Exit fullscreen mode

Grouping by noun means related tools sit together when the client lists them. Plural nouns for collections and singular nouns for one item tells the agent whether it is about to get one thing or many. It looks pedantic. It also heads off a whole category of "I listed when I meant to read" mistakes.

2. Write descriptions for someone who will take them literally

The description is the only documentation the agent reliably reads. So write it like a contract.

Bad:

{
  "name": "document_save",
  "description": "Saves a document."
}
Enter fullscreen mode Exit fullscreen mode

Better:

{
  "name": "document_save",
  "description": "Save the FULL text of a document to the user's files. Do not summarize or shorten the content. Returns the saved document's id. Use document_read to fetch it later."
}
Enter fullscreen mode Exit fullscreen mode

The second version answers the questions the agent would ask if it could: what exactly goes in, what comes back and which tool comes next. The capital letters are not decoration. "FULL" is the word that stops the 40-word summary.

3. Make bad arguments impossible, not just unlikely

Free text arguments are where agents get creative, and you do not want creativity in a date field.

Use enums, formats and bounds wherever you can:

{
  "type": "object",
  "properties": {
    "title": { "type": "string", "minLength": 3, "maxLength": 120 },
    "due": { "type": "string", "format": "date", "description": "YYYY-MM-DD in the user's timezone" },
    "priority": { "type": "string", "enum": ["low", "normal", "high"] }
  },
  "required": ["title"]
}
Enter fullscreen mode Exit fullscreen mode

Every constraint in the schema is one less instruction you need in the prompt. It is also one less thing that breaks when you switch models, because the schema means the same thing to all of them.

4. Error messages are instructions

When a tool call fails, the error message goes straight back to the agent. Most of the time, it will try again based on what you told it.

So "400 Bad Request" is a wasted turn. This is better:

due must be a date in YYYY-MM-DD format. You sent "next Friday".
Resolve relative dates first, then call reminder_add again.
Enter fullscreen mode Exit fullscreen mode

Treat every error string as a tiny prompt. Say what was wrong, what was expected and what to do next. Agents recover far more often when they're told how.

5. Separate "prepare" from "do" for anything with side effects

If a tool sends an email, spends money or deletes something, split it in two. One tool prepares the action and returns a summary. A second tool executes it only after the user approves.

In our server that is a pending action plus action_approve and action_cancel. The agent can be as eager as it likes. Nothing irreversible happens without a human click. I wrote about this pattern in more detail here, so I'll keep it short.

6. Keep the toolset small

It is tempting to expose every endpoint you have. Resist it. Each extra tool is another option the agent has to consider on every turn, and similar-looking tools get confused with each other.

A good test: if two tools would have nearly the same description, merge them or rename them until the difference is obvious from the name alone.

7. Test with the dumbest prompt you can think of

The best test cases are lazy user requests. "Remind me about the thing." "Save this." "What are my goals" with no question mark and no context.

Run those against your server with a couple of different models. Watch which tool gets picked and what arguments come back. You will find the ambiguous names and missing constraints very quickly, usually in a way that makes you laugh and then sigh.

The short version

  • Boring, consistent noun_verb names.
  • Descriptions written as contracts.
  • Schemas that make bad input impossible.
  • Error messages that tell the agent what to do next.
  • Prepare and approve for anything with side effects.
  • Fewer tools than you think you need.

The models will keep getting smarter. Your tools will still be read by a very literal intern. Design for the intern and everyone wins.


I'm Head of SI Agents at Magnus, where we run an MCP server that connects any MCP client to a user's goals, documents and reminders. This post was drafted with help from an SI writing assistant and edited by me.

Top comments (0)