Claude Track
Module 14
Claude Track — Module 14
ShopMate Gets Real Answers: Until now, Claude could only answer questions it already knew. The team enabled MCP tools in Claude Code, and suddenly Claude could look up a live order number, check real stock levels, and read the latest customer ticket — all from the chat panel, no context switching.

Tools & Agentic Tasks in Claude Code

Claude Code can do more than answer questions — it can take actions. It reads and writes files, runs terminal commands, uses MCP-connected tools, and chains multiple steps together autonomously. This module covers every type of tool Claude can use, how to define custom tools, how multi-tool orchestration works, and how to handle errors in tool calls.

File System Actions

Claude can read any file you share via @, create new files, and apply edits across multiple files in one request. Ask: "Add error handling to every function in @utils.ts" — Claude does it all at once. It can also create directories, rename files, and manage project structure.

Terminal & Commands

Claude opens the VS Code integrated terminal and runs commands — installs packages, runs tests, starts servers, executes scripts. It reads the output and continues working based on the result. If a command fails, Claude diagnoses the error and retries.

Web Search

Claude Code can search the web mid-conversation. Ask: "What is the current Stripe webhook signature format?" Claude searches, reads the relevant docs, and answers — without you leaving VS Code. This is essential for staying current on rapidly-changing APIs.

MCP Tools

Model Context Protocol (MCP) connects Claude to external systems — your database, CRM, order management system, or any API. Once configured, you can ask Claude to query live data directly from chat. MCP is an open protocol, so you can build custom servers for any data source.

The Agentic Loop — How Claude Chains Steps

The most powerful aspect of Claude Code is its ability to work through multi-step tasks autonomously. This is called the "agentic loop" — Claude plans, acts, observes results, and iterates.

1

You give a high-level task

"Add a discount code field to the checkout form, hook it up to our promotions table, and write a test for it." This is a task with at least 5 sub-steps — Claude figures out the steps.

2

Claude plans the steps

Claude reads @checkout.tsx, @promotions.ts, and the database schema — then outlines what it intends to do before acting. You can approve or redirect at this point. This planning step is where Claude's reasoning ability matters most.

3

Claude executes and checks

It edits the form component, updates the API route, runs the tests, reads the output. If a test fails, it fixes the error and re-runs without being asked. This observe-act-iterate cycle can run for 10+ steps on complex tasks.

4

Claude reports back

Once all steps complete, Claude summarises what it changed and flags anything it wasn't certain about — giving you a clear handoff for review.

i
Human-in-the-Loop Control

Claude Code asks for permission before taking potentially destructive actions (deleting files, running unfamiliar commands). You can configure the permission level: always ask, ask for new tools, or auto-approve for trusted actions. For production work, keep the default "ask" mode — a 2-second approval click prevents minutes of cleanup.

Defining Tools for Claude (API)

When building applications with the Anthropic API, you can define custom tools that Claude can call. A tool is simply a function described with a JSON schema that tells Claude what the function does, what parameters it expects, and what it returns.

Python — Defining Tools for the API
import anthropic, json
client = anthropic.Anthropic()

# Define tools as JSON schemas — Claude will call these when needed
tools = [
    {
        "name": "lookup_order",
        "description": "Look up a ThreadCo order by order ID. Returns order status, tracking number, and delivery estimate.",
        "input_schema": {
            "type": "object",
            "properties": {
                "order_id": {
                    "type": "string",
                    "description": "The order ID (e.g., 'ORD-4821')"
                }
            },
            "required": ["order_id"]
        }
    },
    {
        "name": "check_stock",
        "description": "Check current stock level for a product by SKU.",
        "input_schema": {
            "type": "object",
            "properties": {
                "sku": {"type": "string", "description": "Product SKU (e.g., 'SGT-M-BLU')"}
            },
            "required": ["sku"]
        }
    },
    {
        "name": "send_email",
        "description": "Send an email to a customer. Requires human approval before sending.",
        "input_schema": {
            "type": "object",
            "properties": {
                "to": {"type": "string", "description": "Recipient email address"},
                "subject": {"type": "string"},
                "body": {"type": "string"}
            },
            "required": ["to", "subject", "body"]
        }
    }
]
!
Tool Description Quality Matters

The description field is the most important part of a tool definition. Claude decides which tool to call based primarily on the description. A vague description like "look up stuff" will cause Claude to call the wrong tool. A specific description like "Look up a ThreadCo order by order ID. Returns order status, tracking number, and delivery estimate" tells Claude exactly when and how to use it.

The Function Calling Loop

When Claude decides it needs to use a tool, the API returns a special tool_use response. Your code must execute the tool and send the result back. This loop continues until Claude has all the information it needs.

Python — Complete Tool Use Loop
def execute_tool(name: str, input: dict) -> str:
    """Execute a tool call and return the result as a string."""
    if name == "lookup_order":
        # In production, this queries your actual database
        return json.dumps({
            "order_id": input["order_id"],
            "status": "shipped",
            "tracking": "GB12345678",
            "carrier": "Royal Mail",
            "estimated_delivery": "2026-04-17"
        })
    elif name == "check_stock":
        return json.dumps({"sku": input["sku"], "in_stock": 23, "warehouse": "London"})
    else:
        return json.dumps({"error": f"Unknown tool: {name}"})

def chat_with_tools(user_message: str) -> str:
    """Run a conversation with tool use, handling the full loop."""
    messages = [{"role": "user", "content": user_message}]

    while True:
        resp = client.messages.create(
            model="claude-sonnet-4-6", max_tokens=1024,
            tools=tools, messages=messages
        )

        # If Claude wants to use tools, execute them and continue
        if resp.stop_reason == "tool_use":
            # Add Claude's response (contains tool_use blocks)
            messages.append({"role": "assistant", "content": resp.content})

            # Execute each tool call and add results
            tool_results = []
            for block in resp.content:
                if block.type == "tool_use":
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })

            messages.append({"role": "user", "content": tool_results})
        else:
            # Claude is done — return the final text response
            return resp.content[0].text

# Usage:
answer = chat_with_tools("Customer #4821 is asking where their order is. Look it up and draft a reply.")
print(answer)

Multi-Tool Orchestration

Claude can call multiple tools in a single turn and use the results together. This enables complex workflows:

Chat Panel — Multi-Tool Example
You:
Customer Maya Johnson (order #4821) is asking if she can exchange
her Sunset Gradient Tee (size M) for a size L. Check:
1. Her order status (is it still exchangeable?)
2. Stock for SKU SGT-L-AMB (Sunset Gradient, Large, Amber)
3. If both are good, draft the exchange confirmation email.

Claude's internal process:
Step 1: Call lookup_order("ORD-4821") → Order delivered 3 days ago, within return window
Step 2: Call check_stock("SGT-L-AMB") → 23 in stock
Step 3: Both conditions met → Draft email using send_email tool
        (but waits for human approval before sending)
Orchestration PatternDescriptionExample
SequentialTool B depends on Tool A's resultLook up order → Use tracking number to check carrier status
ParallelMultiple tools called independently in one turnCheck stock for 3 different SKUs simultaneously
ConditionalTool B is only called if Tool A's result meets a conditionCheck stock → Only draft restock email if stock < 10
IterativeSame tool called multiple times with different inputsCheck stock for each item in a customer's cart
FallbackIf Tool A fails, try Tool B as an alternativeIf order lookup fails, search by customer email instead

Error Handling in Tool Calls

Tool calls can fail — the database might be down, the API might return an error, or the input might be invalid. How you return errors to Claude determines whether it recovers gracefully or spirals into confusion.

Python — Robust Tool Execution with Error Handling
def execute_tool_safely(name: str, input: dict) -> dict:
    """Execute a tool with proper error handling for Claude."""
    try:
        result = execute_tool(name, input)
        return {"type": "tool_result", "content": result}
    except ValueError as e:
        # Input validation error — tell Claude what was wrong
        return {
            "type": "tool_result",
            "content": json.dumps({
                "error": "invalid_input",
                "message": str(e),
                "hint": "Check the parameter format and try again"
            }),
            "is_error": True  # Tells Claude this is an error, not a result
        }
    except ConnectionError:
        # Service unavailable — Claude should tell the user, not retry endlessly
        return {
            "type": "tool_result",
            "content": json.dumps({
                "error": "service_unavailable",
                "message": "The order database is temporarily unavailable",
                "suggestion": "Inform the customer and offer to follow up"
            }),
            "is_error": True
        }
    except Exception as e:
        # Unexpected error — log it and give Claude a generic message
        print(f"Tool error: {name} — {e}")  # Log for debugging
        return {
            "type": "tool_result",
            "content": json.dumps({"error": "internal_error", "message": "An unexpected error occurred"}),
            "is_error": True
        }
!
The Retry Loop Trap

If a tool call fails and you return a vague error, Claude will often retry the same call with the same input — creating an infinite loop. Always include specific information about why the call failed and what Claude should do instead (try different parameters, inform the user, use a fallback tool). The is_error: true flag tells Claude this is an error, not a valid result.

Setting Up MCP Tools for ThreadCo

VS Code settings.json — MCP configuration
// .vscode/settings.json — add MCP servers for Claude Code
{
  "claude.mcpServers": {
    "threadco-orders": {
      "command": "node",
      "args": ["./mcp-servers/orders-server.js"],
      "description": "Look up ThreadCo order status and tracking info"
    },
    "threadco-stock": {
      "command": "node",
      "args": ["./mcp-servers/stock-server.js"],
      "description": "Check live stock levels by SKU"
    }
  }
}
Chat Panel — Using MCP Tools
You (in Claude Code chat):
Customer #4821 is asking where their order is.
Look up the order and draft a reply email.

Claude (uses threadco-orders MCP tool automatically):
Order #4821 — Maya Johnson
Status: Shipped
Carrier: Royal Mail, tracking GB12345678
Estimated delivery: Thursday 17 April

Draft reply:
"Hi Maya, your order shipped yesterday via Royal Mail.
Tracking number: GB12345678 — expected Thursday. Let us know
if it doesn't arrive by Friday and we'll investigate."

Safety Guardrails for Tool Use

Read vs Write Tools

Separate your tools into read-only (lookup, search, check) and write (send email, update database, delete). Allow Claude to use read tools freely but require human confirmation for write tools. This prevents Claude from taking irreversible actions.

Input Validation

Validate all tool inputs before execution. Check types, ranges, and formats. A malformed order ID or SQL injection attempt should be caught by your tool code, not by Claude's judgment. Never trust Claude's input — validate it the same way you'd validate user input.

Rate Limiting

Limit how many tool calls Claude can make per conversation or per minute. Without limits, a confused Claude might make hundreds of API calls trying to recover from an error. Set a maximum of 10-20 tool calls per conversation for most use cases.

Audit Logging

Log every tool call: what was called, with what parameters, what was returned, and whether it was successful. This is essential for debugging, security auditing, and understanding how Claude uses your tools in practice.

i
MCP is Covered in Depth in Module 16

Module 16 (MCP In Depth) walks through configuring MCP servers, available server types, and how to build a custom MCP server for your own systems.

Hands-On Exercises

i
Exercise 1 — Agentic Task

Give Claude Code a multi-step task in your project: "Add input validation to @api/orders.ts, write a test for the validation, and run the tests." Watch the agentic loop in action. Count the steps Claude takes. Did it plan before acting? Did it handle any errors that arose?

i
Exercise 2 — Tool Definition

Design a tool definition (JSON schema) for a function your team uses regularly. Write the name, description, and input_schema. Test whether Claude calls it correctly by describing a scenario where the tool should be used. Refine the description until Claude uses it reliably.

i
Exercise 3 — Error Handling

Using the tool use loop code from this module, simulate three error scenarios: (a) invalid input, (b) service unavailable, (c) unexpected error. Return appropriate error messages to Claude. Does Claude handle each error gracefully? Does it inform the user appropriately? Adjust your error messages until Claude's recovery behaviour is satisfactory.

i
Exercise 4 — MCP Setup

If you have not already, set up one MCP server in your VS Code settings. Use a pre-built server (filesystem, SQLite, or GitHub) from the MCP server registry. Test it by asking Claude a question that requires the tool. Verify that Claude calls it automatically and uses the result correctly.

i
Exercise 5 — Multi-Tool Workflow

Design a customer service workflow that requires at least 3 tool calls: (1) look up order, (2) check stock for replacement, (3) draft response email. Implement the tools (even with mock data) and run the full workflow through Claude. Track whether Claude calls the tools in the correct order and combines the results coherently.