Study guides / CCAR-F / Domain 2

Tool Design & MCP Integration · Lesson 3 of 5

2.3 - Tool Distribution & Tool Choice

Distributing tools across agents and configuring tool_choice for reliable tool selection

The number of tools you give an agent directly affects how reliably it selects the right one. That sounds like an implementation detail. It isn't - it's an architectural decision that determines whether your multi-agent system works in production.

The Tool Overload Problem

Giving a single agent 18 tools degrades selection reliability. Every additional tool adds decision complexity, and error rates climb as the toolkit grows. The optimal range is 4-5 tools per agent, scoped to that agent's specific role.

Quantity isn't the whole story, though - relevance matters just as much. A synthesis agent should NOT have web search tools. A web search agent should NOT have document analysis tools. Give an agent tools outside its specialisation and it will tend to misuse them: a synthesis agent with access to web_search might run its own searches instead of using the results already handed to it, duplicating work and wasting context.

The principle: each agent gets only the tools it needs for its defined role. Nothing more.

Consolidating Near-Duplicate Tools

Splitting by role is the obvious answer to tool overload. It's the wrong one when the tools all do the same kind of work.

Take a data platform server with 22 tools: three query tools, one per data source, and 19 transformations - pivot_table, calculate_percentile, normalise_currency, on down the list. Split that by role and you hand a transformation agent 19 tools, which is the original problem moved one level down. The agent still can't choose reliably.

Those 19 collapse instead, because they share a shape. Data in, an operation, data out:

{
  "name": "transform_data",
  "description": "Apply a transformation to a dataset. Use transform_type to select the operation.",
  "input_schema": {
    "type": "object",
    "properties": {
      "dataset": { "type": "string" },
      "transform_type": {
        "type": "string",
        "enum": ["pivot", "percentile", "normalise_currency", "..."]
      },
      "options": { "type": "object" }
    },
    "required": ["dataset", "transform_type"]
  }
}

Twenty-two tools become four. Nothing is lost: every transformation is still reachable, now as an enum value the model picks inside a single call rather than a tool it has to find among nineteen near-identical descriptions. Selection accuracy improves because the hard choice got smaller, not because the capability did.

So which fix applies?

The tools are... The fix
Few enough to handle, but two of them read alike Sharpen the descriptions (Task Statement 2.1)
Different jobs (query, transform, export) Split by role, 4-5 tools each
Variations on one job, sharing a shape Consolidate into one parameterised tool
Doing more than the agent should be able to do Constrain them (next section)

The first row is the one candidates trip on. Task Statement 2.1 teaches descriptions as the fix for misrouting, and it is right when the toolkit is small enough to reason about. An agent choosing get_customer over lookup_order from a set of five is a description problem. The same symptom from a set of 22 is not: the agent is past the point where any description quality rescues selection, and rewriting all 22 leaves the decision complexity exactly where it was. Same symptom, different disease. Count the tools before you pick the remedy.

Watch the third row: it pulls the other way, and the exam likes that tension. Consolidation reduces how many tools an agent chooses between. Constraining reduces what any one tool can reach. Collapsing 19 transformations into transform_data doesn't hand the agent new powers, so it doesn't undo least privilege. Replacing fetch_url with load_document does the opposite job and both can be right in the same system.

One fix that isn't a fix: moving tools onto a second MCP server. Server boundaries are invisible to the model. A client hands it every tool from every connected server as one flat list, so a 22-tool problem split across two servers is still a 22-tool problem.

The tool_choice Configuration

The tool_choice parameter controls how the model interacts with available tools. Three settings, three distinct jobs.

"auto" (default) The model decides whether to call a tool or return text. Use this for general operation where the model needs flexibility to respond conversationally when no tool call is appropriate.

{
  "tool_choice": { "type": "auto" }
}

"any" The model MUST call a tool but chooses which one. Use this when you need guaranteed structured output from one of multiple schemas - the model will always produce a tool call, never plain text.

{
  "tool_choice": { "type": "any" }
}

Extraction pipelines are where this earns its keep. If you have multiple extraction schemas (one for invoices, one for receipts, one for contracts) and the document type is unknown, "any" guarantees the model picks one and produces structured output rather than returning a conversational response.

Forced selection The model MUST call a specific named tool. Use this to enforce mandatory first steps - the model cannot skip or reorder the required operation.

{
  "tool_choice": { "type": "tool", "name": "extract_metadata" }
}

This is the tool for enforcing workflow ordering. If metadata extraction must happen before any enrichment tools run, forced selection guarantees it. The model can't decide to skip extract_metadata and jump straight to enrichment. After the forced call completes, subsequent turns can use "auto" for the remaining steps.

Scoped Cross-Role Tools

Sometimes an agent needs occasional access to a capability that belongs to another role. The naive approach is to route every such request through the coordinator. The problem: this adds 2-3 round trips per request and can increase latency by 40% or more.

The solution is a scoped cross-role tool: a constrained version of the capability, given directly to the agent that needs it.

Say a synthesis agent needs to verify simple facts constantly during report generation. The naive design routes every verification back to the coordinator, which delegates to the search agent, waits for results, and returns them. For 85% of verifications - simple lookups that take milliseconds - that round trip is pure waste.

The fix: give the synthesis agent a scoped verify_fact tool that handles simple lookups directly. Complex verifications (requiring multiple sources, cross-referencing, or real judgement) still route through the coordinator. The 85% simple case is handled locally; the 15% complex case uses the full pipeline.

The exam tests this pattern directly (Q9).

Replacing Generic Tools with Constrained Alternatives

Instead of giving a subagent fetch_url (which can fetch anything from anywhere), give it load_document that validates document URLs only. The constrained tool:

This is least privilege applied to tool design. Each tool does exactly what the agent needs and nothing more.

Role-Specific Tool Scoping in Practice

Here is how tool distribution looks in a well-designed multi-agent research system:

Agent Tools (4-5 each)
Web Search search_web, fetch_page, extract_links, save_snippet
Document Analysis extract_metadata, extract_data_points, summarize_content, verify_claim
Synthesis compile_report, verify_fact (scoped), format_citation, assess_coverage
Coordinator Agent (formerly Task, used to spawn subagents), review_output, request_revision

Each agent has exactly the tools it needs. The synthesis agent has a scoped verify_fact for simple lookups. The coordinator runs the workflow without holding any domain-specific tools itself.

Key Concept

The optimal range is 4-5 tools per agent, scoped to its role. For high-frequency simple operations, add a scoped cross-role tool directly to the agent that needs it - this avoids coordinator round-trip latency for the common case.

Exam traps

Practice question

A synthesis agent frequently returns control to the coordinator for simple fact verification, adding 2-3 round trips per task and 40% latency. Analysis shows 85% of verifications are simple lookups. What is the most effective solution?

  • A Give the synthesis agent a scoped verify_fact tool for simple lookups, routing only complex verifications through the coordinator. Correct

    A scoped cross-role tool handles the 85% simple case directly, eliminating round-trip latency. Complex cases still route through the coordinator for proper handling.

  • B Increase the coordinator parallelism so that verification requests are processed concurrently and the queueing delay disappears entirely.

    Faster processing does not eliminate unnecessary round trips. The latency comes from the routing overhead itself, not the coordinator speed.

  • C Cache all verification results at the coordinator level so that repeated lookups return instantly without a second round trip to any subagent.

    Caching helps with repeated lookups but does not address the fundamental round-trip overhead for first-time verifications, which constitute the majority.

  • D Remove the fact verification step from the synthesis workflow entirely so no task ever pays the round-trip latency.

    Removing verification compromises output quality. The goal is to make verification faster for the common case, not to skip it entirely.

Build exercise: Configure Tool Distribution Across a Multi-Agent System

Intermediate · 45 minutes

You'll practice:

  1. Design three agent roles (web search, document analysis, synthesis) and assign 4-5 tools to each, scoped to its role

    Tool overload degrades selection reliability. The exam tests the principle that each agent should have 4-5 tools scoped to its specific role. Giving a single agent 18 tools is a known anti-pattern that causes misrouting.

    You should see: A configuration object or table listing three agents, each with exactly 4-5 tools. No tool appears in more than one agent role (except scoped cross-role tools added later). Tool names clearly indicate their purpose and scope.

    Hints
    1. Ask yourself for each tool: does this belong to web search, document analysis, or synthesis? If a tool could fit two roles, it probably needs to be split or scoped.
    2. Map tools to roles: Web Search gets search_web, fetch_page, extract_links, save_snippet. Document Analysis gets extract_metadata, extract_data_points, summarise_content, verify_claim. Synthesis gets compile_report, format_citation, assess_coverage.
    3. const agentToolsets = {
        webSearch: {
          role: "Finds and retrieves web content",
          tools: [
            { name: "search_web", description: "Searches the web for a query and returns ranked results" },
            { name: "fetch_page", description: "Fetches the full content of a web page by URL" },
            { name: "extract_links", description: "Extracts all hyperlinks from a web page" },
            { name: "save_snippet", description: "Saves a text snippet with source URL for later use" }
          ]
        },
        documentAnalysis: {
          role: "Analyses document structure and content",
          tools: [
            { name: "extract_metadata", description: "Extracts title, author, date, and document type" },
            { name: "extract_data_points", description: "Extracts structured data fields (dates, amounts, names)" },
            { name: "summarise_content", description: "Produces a concise summary of key arguments" },
            { name: "verify_claim", description: "Checks if a claim is supported by the source document" }
          ]
        },
        synthesis: {
          role: "Compiles findings into reports",
          tools: [
            { name: "compile_report", description: "Assembles research findings into a structured report" },
            { name: "format_citation", description: "Formats a source reference in the required citation style" },
            { name: "assess_coverage", description: "Evaluates whether all research questions have been addressed" }
          ]
        }
      };
  2. Add a scoped verify_fact tool to the synthesis agent that handles simple lookups directly

    Routing every fact verification through the coordinator adds 2-3 round trips and up to 40% latency. The exam tests the scoped cross-role tool pattern - give the agent a constrained version of a capability for the 85% simple case, routing only complex cases to the coordinator.

    You should see: A verify_fact tool added to the synthesis agent toolset with a description that explicitly limits it to simple single-source lookups and states that complex multi-source verifications should be escalated to the coordinator.

    Hints
    1. The scoped tool should have clear boundaries - it handles simple lookups only. Include an explicit boundary in the description about when to escalate to the coordinator instead.
    2. Define the tool with parameters for the claim to verify and the source to check against. The description should state it handles single-source simple lookups and not multi-source cross-referencing.
    3. // Add to synthesis agent tools
      const scopedVerifyFact = {
        name: "verify_fact",
        description: "Verifies a simple factual claim against a single source document. Use for quick checks during report compilation. For complex verifications requiring multiple sources or cross-referencing, escalate to the coordinator.",
        inputSchema: {
          type: "object",
          properties: {
            claim: { type: "string", description: "The factual claim to verify" },
            sourceId: { type: "string", description: "ID of the source document to check against" }
          },
          required: ["claim", "sourceId"]
        }
      };
      agentToolsets.synthesis.tools.push(scopedVerifyFact);
  3. Configure tool_choice forced selection on the document analysis agent to ensure extract_metadata runs as the mandatory first step

    Forced selection enforces workflow ordering. The exam tests your knowledge of all three tool_choice modes: auto lets the model choose freely, any guarantees a tool call, and forced selection guarantees a specific tool call. This prevents the model from skipping mandatory steps.

    You should see: A document analysis agent configuration where the first API call uses tool_choice with type: tool and name: extract_metadata, and subsequent calls switch to tool_choice: auto for the remaining analysis steps.

    Hints
    1. Forced selection applies only to the first turn. After extract_metadata completes, switch to auto so the model can choose the appropriate analysis tool for subsequent steps.
    2. Set tool_choice to { type: "tool", name: "extract_metadata" } for the first message, then change to { type: "auto" } for follow-up messages in the agent loop.
    3. // First turn: forced selection
      const firstTurn = await client.messages.create({
        model: "claude-sonnet-5",
        max_tokens: 1024,
        tools: agentToolsets.documentAnalysis.tools,
        tool_choice: { type: "tool", name: "extract_metadata" },
        messages: [{ role: "user", content: "Analyse this document: ..." }]
      });
      // Subsequent turns: auto selection
      const nextTurn = await client.messages.create({
        model: "claude-sonnet-5",
        max_tokens: 1024,
        tools: agentToolsets.documentAnalysis.tools,
        tool_choice: { type: "auto" },
        messages: [...previousMessages]
      });
  4. Replace a generic fetch_url tool with a constrained load_document that validates document URLs only

    This applies the principle of least privilege to tool design. A generic fetch_url tool can fetch anything from anywhere, enabling misuse. A constrained load_document that validates URLs prevents the agent from fetching arbitrary resources. The exam tests this pattern directly.

    You should see: A load_document tool definition that includes URL validation logic (checking for document file extensions or trusted domains) and rejects non-document URLs with a clear error message.

    Hints
    1. Think about what makes a URL a document URL - file extensions like .pdf, .docx, .md, or specific trusted domains. The tool should reject anything outside these patterns.
    2. Implement a URL validation function that checks the URL against an allowlist of document patterns. Return a structured error if the URL does not match.
    3. const loadDocument = {
        name: "load_document",
        description: "Loads a document from a validated URL. Only accepts URLs ending in .pdf, .docx, .md, .txt, or .html from trusted domains. Use instead of fetch_url for document retrieval.",
        handler: async ({ url }: { url: string }) => {
          const validExtensions = [".pdf", ".docx", ".md", ".txt", ".html"];
          const trustedDomains = ["docs.internal.com", "wiki.company.com"];
          const parsed = new URL(url);
          const hasValidExt = validExtensions.some(ext => parsed.pathname.endsWith(ext));
          const isTrusted = trustedDomains.includes(parsed.hostname);
          if (!hasValidExt || !isTrusted) {
            return {
              isError: true,
              content: [{ type: "text", text: `Rejected: ${url} is not a valid document URL. Must be a document file from a trusted domain.` }]
            };
          }
          // Fetch and return document content
          return { content: [{ type: "text", text: `Document content from ${url}` }] };
        }
      };
  5. Test with a query that requires all three agents and verify that no cross-role tool misuse occurs

    End-to-end testing validates that your tool distribution works in practice. Cross-role misuse - such as a synthesis agent running its own web searches instead of using provided results - is a common failure the exam expects you to prevent through proper scoping.

    You should see: A test run log showing: the web search agent using only its tools, the document analysis agent starting with extract_metadata (forced), and the synthesis agent using compile_report plus verify_fact for simple checks. No agent calls a tool outside its assigned set.

    Hints
    1. Run a query like "Research the latest MCP specification changes and compile a summary report" - this naturally requires all three agent roles.
    2. Log every tool call with the agent name and tool name. After the run, verify that each tool call came from the correct agent. Any cross-role calls indicate a scoping problem.
    3. async function runMultiAgentQuery(query: string) {
        const log: Array<{ agent: string; tool: string }> = [];
        // Simulate coordinator dispatching to agents
        for (const [agentName, config] of Object.entries(agentToolsets)) {
          const response = await client.messages.create({
            model: "claude-sonnet-5",
            max_tokens: 1024,
            tools: config.tools, // Only this agent's tools
            messages: [{ role: "user", content: query }]
          });
          for (const block of response.content) {
            if (block.type === "tool_use") {
              log.push({ agent: agentName, tool: block.name });
            }
          }
        }
        // Verify no cross-role usage
        for (const entry of log) {
          const agentTools = agentToolsets[entry.agent].tools.map(t => t.name);
          console.log(`${entry.agent}: ${entry.tool} - ${agentTools.includes(entry.tool) ? "VALID" : "CROSS-ROLE VIOLATION"}`);
        }
      }

Sources