Contents26 sections
Chapter 5: Function Calling, Tool Use, and Model Context Protocol (MCP)
Function Calling and Model Context Protocol (MCP) transform LLMs from conversational chatbots into autonomous systems capable of querying information and performing operations. However, invoking external capabilities introduces software engineering challenges: permission control, parameter schema validation, idempotency, side-effect isolation, protocol security, and comprehensive evaluation.
1. Tools as Governed Capability Interfaces
Models should never receive direct raw database connections, operating system shells, or arbitrary outbound HTTP capabilities. Expose business capabilities strictly as narrow interfaces:
Discouraged: execute_sql(sql: string)
Recommended: get_order_status(order_id: string)
Discouraged: http_request(url, method, body)
Recommended: create_refund_request(order_id, reason, amount)Advantages of narrow interfaces:
- Unambiguous authorization and privilege boundaries;
- Strict parameter schema validation;
- Full telemetry observability and regression testability;
- Streamlined implementation of idempotency keys and approval gates;
- Minimized blast radius of Prompt Injections executing arbitrary operations.
🔥 P0 High-Frequency Essential: Why avoid giving an Agent a generic SQL tool? Generic SQL confers excessive blast radius, making it nearly impossible to govern query intent, resource exhaustion, and PII exfiltration risks. Always favor narrow read-only business tools; when raw SQL is unavoidable, enforce read-only credentials, table/column whitelists, AST query parsing, row/timeout caps, tenant filters, and immutable audit logs.
2. Six Criteria for High-Quality Tool Schemas
- Tool name expresses a single, atomic action;
- Docstrings explicitly define "when to use" and "when NOT to use";
- Input types, enums, string lengths, and formats are strictly bounded;
- Output schemas maintain deterministic structural stability;
- Errors return machine-readable error codes rather than free-form text;
- Side-effects and human approval requirements are identifiable by the runtime.
from decimal import Decimal
from typing import Literal
from pydantic import BaseModel, Field
class RefundRequest(BaseModel):
order_id: str = Field(pattern=r"^ORD-[0-9]{8}$")
reason: Literal["duplicate_charge", "not_received", "quality_issue", "other"]
amount: Decimal = Field(gt=0, max_digits=10, decimal_places=2)
idempotency_key: str = Field(min_length=16, max_length=128)
class RefundResult(BaseModel):
request_id: str
status: Literal["pending_review", "approved", "rejected"]
message: strNever represent financial amounts as binary floats; never validate order IDs merely as "non-empty string"; and ensure idempotency keys are generated or verified by trusted backend runtime code rather than letting the LLM invent random keys to bypass deduplication.
3. Tool Return Payloads Must Facilitate Self-Correction
Errors should never return unstructured natural language alone:
from typing import Literal
from pydantic import BaseModel
class ToolError(BaseModel):
code: Literal[
"NOT_FOUND",
"PERMISSION_DENIED",
"INVALID_STATE",
"RATE_LIMITED",
"TEMPORARY_UNAVAILABLE",
]
retryable: bool
user_fixable: bool
message: str
missing_fields: list[str] = []The Agent runtime inspects retryable for backoff retries and user_fixable to prompt for user clarification; PERMISSION_DENIED must fail immediately without passing errors back for the model to "attempt bypassing."
4. End-to-End Execution Loop for Tool Calling
While provider SDK syntax varies, application runtime logic remains universal:
async def run_tool_loop(model, tool_registry, messages, max_steps=6):
for step in range(max_steps):
response = await model.respond(
messages=messages,
tools=tool_registry.schemas(),
)
if response.final_text is not None:
return response.final_text
for call in response.tool_calls:
tool = tool_registry.get(call.name)
# # 1. Tool allowlist validation
if tool is None:
result = {"error": {"code": "UNKNOWN_TOOL", "retryable": False}}
else:
# # 2. Schema validation
args = tool.input_model.model_validate(call.arguments)
# # 3. IAM permissions and risk policy
decision = await authorize_tool(tool, args)
if decision.requires_approval:
return await pause_for_approval(call, decision)
# # 4. Timeout, idempotency, and audit logging handled by executor
result = await tool_executor.execute(tool, args)
# # 5. Tool results and call_id must be mapped back
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result,
})
raise RuntimeError("agent exceeded maximum tool steps")A proposed model tool call does not obligate the system to execute it. Validation, authentication, authorization, approval gates, and rate budgets occur strictly in trusted backend code.
🔥 P0 High-Frequency Essential: What checks must be performed before executing a tool call? Verify tool whitelist membership; validate argument schemas; verify user identity and resource-level permissions; validate current business workflow state; check risk classification and approval gates; verify idempotency keys; check rate/cost budgets; ensure multi-tenant boundary isolation; and apply data masking to sensitive fields.
5. Tool Risk Classification Matrix
| Risk Tier | Example Capabilities | Recommended Controls |
|---|---|---|
| R0 Public Read-Only | Query public weather, public documentation | Rate limiting, timeouts, standard logging |
| R1 Private Read-Only | Query user orders, internal private knowledge | User auth, tenant isolation, field-level ACL, audit log |
| R2 Reversible Mutation | Create draft, file support ticket | Idempotency keys, approval policy, rollback action |
| R3 External Side-Effect | Send email, alter calendar, submit refund | Explicit user confirmation, impact preview, immutable audit |
| R4 Irreversible / High-Risk | Delete database records, execute code, transfer funds | Default-deny, mandatory human approval, sandbox, 2-man rule |
Risk tiers must be codified in static tool metadata rather than delegating self-assessment of danger to LLM reasoning.
6. Idempotency, Transactions, and Compensating Actions
Reliable tool execution requires three foundational software mechanics:
- Idempotency: Re-invoking an action with identical parameters yields identical side-effects; this is a critical execution standard and core baseline in production engineering architecture and system design.
- Transactions: A bundle of local operations commits atomically or rolls back entirely; this is a critical execution standard and core baseline in production engineering architecture and system design.
- Compensating Actions: Explicit rollback actions when distributed operations cannot achieve atomic rollback. This is a critical execution standard and core baseline in production engineering architecture and system design.
For example, "Book flight and create calendar event" spans two distributed third-party systems without distributed two-phase commits. Pattern: lock itinerary, persist pending record, book flight after user authorization, create calendar event on success; if calendar sync fails, retry or flag for compensation without cancelling confirmed flight tickets.
⭐ P1: What is the relationship between the Saga pattern and Agents? While Agents plan operational sequences, they must adhere to transactional boundaries. Distributed workflows utilize Saga compensating actions to manage partial failures; compensating logic is deterministic software design, never generated ad-hoc by LLMs.
7. What is Model Context Protocol (MCP)?
The Model Context Protocol (MCP) is an open standard connecting AI applications with external capabilities over JSON-RPC. This chapter covers the 2025-11-25 specification standardizing schema validation via JSON Schema.
MCP Servers expose three fundamental primitives:
| Primitive | Controlled By | Primary Purpose |
|---|---|---|
| Tools | Model Requested | Execute dynamic operations or parameterized queries |
| Resources | Application Controlled | Read-only context (e.g., files, DB schemas, docs) |
| Prompts | User Selected | Predefined slash-command workflow templates |
Official documentation can be found at Understanding MCP servers.
🔥 P0 High-Frequency Essential: What is the difference between MCP and Function Calling? Function Calling is a model-level capability to emit structured JSON function calls; MCP is an application-level open protocol for clients to discover and invoke Tools, Resources, and Prompts from decoupled external servers. MCP standardizes integration but does not replace model reasoning, tool selection, or backend authorization.
8. MCP Architecture
User
-> Host (AI Application)
-> MCP Client A -> MCP Server A (Knowledge Base)
-> MCP Client B -> MCP Server B (Ticket System)
-> MCP Client C -> MCP Server C (Calendar)- Host manages model inference, sessions, security boundaries, and UI;
- Client communicates with individual servers, negotiating protocol capabilities;
- Server exposes specific Tools, Resources, and Prompts;
- Servers must never implicitly trust model outputs or untrusted payloads from other servers.
9. Implementing a Minimal FastMCP Server
Official Python documentation leverages FastMCP, where type annotations and docstrings automatically generate tool schemas. Refer to Build an MCP server.
from typing import Literal
from mcp.server.fastmcp import FastMCP
from pydantic import Field
mcp = FastMCP("support-tools")
@mcp.tool()
async def get_ticket_status(
ticket_id: str = Field(pattern=r"^T-[0-9]{6}$"),
) -> dict:
"""Query ticket status. Read-only operation; does not mutate ticket state."""
# Production implementation must extract user_id from auth context, not allow arbitrary user parameters.
return {
"ticket_id": ticket_id,
"status": "open",
"updated_at": "2026-07-22T10:00:00Z",
}
@mcp.tool()
async def create_ticket_draft(
title: str = Field(min_length=5, max_length=120),
category: Literal["billing", "technical", "other"] = "other",
) -> dict:
"""Create a ticket draft without submitting; submission mandates explicit user confirmation in the UI."""
return {
"draft_id": "D-000001",
"title": title,
"category": category,
"status": "draft",
"requires_user_confirmation": True,
}
if __name__ == "__main__":
mcp.run(transport="stdio")Package installation per official SDK guidelines typically uses:
uv add "mcp[cli]"The STDIO Logging Trap
Under STDIO transport, stdout carries protocol JSON-RPC frames. Official documentation strictly warns: never use print() to stdout, as it corrupts JSON-RPC framing. Always stream logs to stderr or dedicated log files.
import logging
import sys
logging.basicConfig(stream=sys.stderr, level=logging.INFO)This is a classic, high-frequency interview gotcha in MCP engineering.
10. Choosing Between Resources and Tools
Use Resources when:
- Data is strictly read-only;
- Host controls when and how context is injected;
- Content resembles static files, schemas, or reference manuals;
- You want to prevent the model from treating reading as an active "action."
Use Tools when:
- Dynamic parameterized queries are required;
- Arguments must be computed at runtime;
- Operations cause mutations or external side-effects;
- Model must autonomously decide whether and when to invoke.
Never return massive unstructured files directly inside Tool outputs. Return Resource URIs or search tools yielding summaries and citations for selective retrieval.
11. MCP Authorization Architecture
For HTTP transport, the 2025-11-25 specification codifies an OAuth 2.1 authorization framework with Protected Resource Metadata. Key security pillars:
- Access tokens must be cryptographically bound to specific target resources;
- Servers must strictly validate Token Audience (
aud); - Strict prohibition against passing inbound bearer tokens downstream;
- Enforce principle of least privilege scopes;
- Mandatory HTTPS, PKCE, short-lived tokens, and secure vault storage;
- Never log tokens, authorization codes, or private keys.
Refer to MCP Authorization. STDIO transport inherits security context from local process execution and environment variables rather than executing OAuth flows.
🔥 P0 High-Frequency Essential: Why is Token Passthrough dangerous? Downstream servers may erroneously accept tokens not minted for them, causing privilege escalation and confused deputy vulnerabilities. An MCP Server must verify that inbound tokens are explicitly scoped for itself, and mint separate downstream tokens when calling external services.
12. Tool Description Injection and Supply Chain Security
MCP Server tool descriptions constitute untrusted external input. Hosts must never connect to arbitrary unverified servers and grant elevated permissions. Enforce:
- Server whitelisting with cryptographic signature and origin verification;
- Transparent UI permission prompts displaying server and tool scopes;
- Mandatory human authorization on high-risk capabilities;
- Static analysis and security audits on tool descriptions and JSON schemas;
- Dependency locking and automated vulnerability scanning;
- Process sandboxing, network egress policies, and strict rate limits;
- Continuous auditing of MCP Server version mutations.
13. Tool Evaluation: Multi-Layer Benchmarking
Benchmark across at least four distinct dimensions:
- Selection Precision: Whether the model accurately selects the correct tool; this is a critical execution standard and core baseline in production engineering architecture and system design.
- Argument Validity: Schema conformity, type adherence, and business constraint validity; this is a critical execution standard and core baseline in production engineering architecture and system design.
- Execution Reliability: Timeout resilience, retry backoff, idempotency, and IAM authorization; this is a critical execution standard and core baseline in production engineering architecture and system design.
- Trajectory Efficiency: Minimizing redundant invocations and excessive intermediate steps. This is a critical execution standard and core baseline in production engineering architecture and system design.
Example evaluation benchmark case:
{
"input": "Check the status of ticket T-123456",
"expected_tool": "get_ticket_status",
"expected_arguments": {"ticket_id": "T-123456"},
"forbidden_tools": ["create_ticket_draft"],
"max_tool_calls": 1
}14. Chapter Exercises
Exercise A: Enterprise Tool Registry
Implement a central Tool Registry validating schemas, risk classification tiers, permission scopes, timeouts, and idempotency keys prior to tool execution.
Exercise B: FastMCP Server
Implement three capabilities: a read-only ticket query Tool, a ticket draft creation Tool, and a support policy Resource. Ensure all STDIO logging routes strictly to stderr.
Exercise C: Security Red-Teaming Suite
Write automated tests verifying: unknown tool handling, unauthorized cross-tenant IDs, idempotency key replay, malicious parameter payloads, tool description prompt injections, duplicate write loops, and approval bypass attempts.
15. High-Frequency Interview Q&A
🔥 P0: How do you design a reliable production Tool?
Scope narrow business capability, enforce strict Pydantic schemas, return structured machine-readable error codes, enforce least-privilege IAM, inject server-side authenticated context, configure timeouts and bounded retries, ensure idempotency, classify risk tiers, enforce approval gates, and maintain audit logs. Validate continuously with tool selection and argument test suites.
🔥 P0: What are MCP Tools, Resources, and Prompts?
Tools are model-requested executable actions; Resources are application-controlled read-only context documents; Prompts are user-selected slash-command templates. They differ fundamentally in control plane ownership; avoid flattening all capabilities into Tools.
🔥 P0: How do you handle tool execution failures?
Classify the error: retry transient network faults with exponential backoff; pass schema errors back for bounded model correction; interrupt for user input on missing parameters; reject immediately on permission violations; and query idempotency status on uncertain mutations before attempting retries.
⭐ P1: Why doesn't MCP automatically solve security challenges?
MCP standardizes discovery, invocation, and authorization protocols, but server trustworthiness, tool over-privileging, human approvals, business parameter validation, and execution sandboxing remain the responsibility of the Host and Server implementations. Standardized protocols do not equate to standardized trust.
⭐ P1: How do you mitigate tool selection degradation when registry sizes grow large?
Dynamically expose minimal tool subsets per intent; route requests prior to tool injection; refine docstring semantic boundaries; filter tools by user permission; benchmark confusion matrices; and merge overlapping tools or replace them with deterministic code rules.
16. Chapter Completion Criteria
- Able to design narrow, typed, and idempotent tool interfaces;
- Able to implement the complete Request—Validate—Authorize—Approve—Execute—Observe loop;
- Able to articulate MCP Host/Client/Server architecture and the three core primitives;
- Able to implement and execute a FastMCP Server with correct STDIO logging;
- Able to explain HTTP OAuth 2.1 authorization, Token Audience verification, and anti-passthrough rules;
- Possess automated test suites covering tool selection, argument validation, IAM boundaries, and deduplication.
REFERENCES
References
Series
AI Agent Development and Interview Guide