Agentic Patterns & MCP for Real Apps

Reach for an agent only when a task is genuinely open-ended, and use MCP to connect Claude to real tools and data through one standard interface.

"Agent" is an overused word. In practice most AI features are a single call or a short tool-use loop — and you should keep them that way. Reach for a full agent only when the task is genuinely open-ended and hard to script.

The ladder of complexity

  • Single call — classify, summarise, extract. One request, one response. Most features live here.
  • Tool-use loop — Claude asks to call your functions, you run them, you feed results back, it answers. This covers "look something up, then respond".
  • Agent — a longer, model-driven loop that decides its own steps. Powerful, but higher cost and latency, and errors need to be catchable. Justify it before you build it.

MCP: one interface to many tools

The Model Context Protocol lets Claude connect to external tools and data sources — your database, GitHub, a search index — through a single standard interface, instead of you hand-wiring each integration. You point Claude at an MCP server and its tools become available. It is the clean way to give an assistant real capabilities in a real product.

Keep the human in control

Whatever the tier, you execute the tools. Gate anything destructive or irreversible behind a confirmation, and log what the model chose to call. Autonomy is a spectrum you dial in, not an on-switch.

Example

Example Β· javascript
// A tool-use LOOP β€” the right tier for 'look it up, then answer'.
// Not a full agent: your code stays in control of every step.
const tools = [{
  name: "get_order_status",
  description: "Look up an order's status. Call when the user asks about an order.",
  input_schema: {
    type: "object",
    properties: { order_id: { type: "string" } },
    required: ["order_id"],
  },
}];

let messages = [{ role: "user", content: userMessage }];
let res = await client.messages.create({ model: "claude-sonnet-5", max_tokens: 1024, tools, messages });

while (res.stop_reason === "tool_use") {
  messages.push({ role: "assistant", content: res.content });
  const results = [];
  for (const block of res.content) {
    if (block.type === "tool_use") {
      // YOU run the tool β€” gate anything destructive behind your own checks.
      const output = await lookupOrder(block.input.order_id);
      results.push({ type: "tool_result", tool_use_id: block.id, content: output });
    }
  }
  messages.push({ role: "user", content: results });
  res = await client.messages.create({ model: "claude-sonnet-5", max_tokens: 1024, tools, messages });
}

When to use it

  • Build a multi-step agent that calls a searchProducts tool, then a checkInventory tool, before answering a user query.
  • Connect Claude to your internal CRM via an MCP server so it can look up account data without custom API glue code.
  • Use a tool-use loop with a human-approval step for any tool that modifies data, such as deleteRecord or sendEmail.

More examples

Two-tool agent loop

Drives the full agent loop until Claude reaches end_turn, executing any number of sequential tool calls.

Example Β· javascript
const tools = [searchTool, inventoryTool];
let messages = [{ role: "user", content: userQuery }];

while (true) {
  const res = await client.messages.create({ model: "claude-sonnet-4-5", max_tokens: 512, tools, messages });
  if (res.stop_reason === "end_turn") { console.log(res.content[0].text); break; }
  const calls = res.content.filter(b => b.type === "tool_use");
  const results = await Promise.all(calls.map(c => runTool(c.name, c.input).then(r => ({
    type: "tool_result", tool_use_id: c.id, content: JSON.stringify(r)
  }))));
  messages = [...messages, { role: "assistant", content: res.content }, { role: "user", content: results }];
}

MCP server config for a CRM

Registers a local MCP server for the CRM so Claude Code can call CRM tools without any custom handler code.

Example Β· json
{
  "mcpServers": {
    "crm": {
      "command": "node",
      "args": ["./mcp/crm-server.js"],
      "env": {
        "CRM_API_KEY": "${CRM_API_KEY}",
        "CRM_BASE_URL": "https://crm.internal.example.com"
      }
    }
  }
}

Human-in-the-loop before destructive tools

Intercepts calls to destructive tools and requires explicit user confirmation before executing them.

Example Β· javascript
async function runTool(name, input) {
  const DESTRUCTIVE = ["delete_record", "send_email", "charge_card"];
  if (DESTRUCTIVE.includes(name)) {
    const confirmed = await promptUser(`Claude wants to call ${name} with ${JSON.stringify(input)}. Allow? (y/n)`);
    if (confirmed !== "y") return { error: "Action cancelled by user" };
  }
  return toolHandlers[name](input);
}

Discussion

  • Be the first to comment on this lesson.