MCP, Tooling, and Agent Interfaces
MCP, Tooling, and Agent Interfaces
TL;DR
- Tools turn a model into an actor; MCP and tool interfaces must be designed as security boundaries, not convenience wrappers.
- Model Context Protocol standardizes how AI clients discover and call external tools, resources, and prompts; a tool implementing MCP can be reused across different agent frameworks.
- A good MCP server exposes narrow, typed capabilities (e.g., “query billing logs” or “create support ticket”) rather than broad shell-like power (e.g., “execute any bash command”).
- Every mutating tool needs idempotency, dry-run support, server-side authorization checks, and immutable audit logs; do not rely on the model to enforce authorization.
- Read tools can be broader in scope; write tools must be scoped narrowly; high-impact tools (payments, deletes, production deploys) require human approval before execution.
Core Concepts
Model Context Protocol (MCP) and Standardization
Model Context Protocol is a standardization layer that defines how an AI client (model + harness + application) discovers and calls external tools, resources, and prompts. Without MCP, every integration is hand-built: the application defines a tool, the model learns about it in the system prompt, the application implements the tool-calling logic. With MCP, a tool server implements the protocol; any MCP-compatible client can discover that tool and call it. This means a tool built for one agent framework (Claude, OpenAI, Bedrock Agents) can be reused in another without rewriting integration code. MCP is the equivalent of REST API standardization for AI agent tool integration.
Tool Scope and Capability Design
A tool is a named, typed capability that an agent can invoke. Poor tool design: “execute” (take any bash command and run it); good tool design: “query_support_tickets” (list tickets, optionally filtered by status, assigned-to, or date range). Poor scope: broad and implicit (assume the agent will know what this tool does); good scope: narrow and explicit (this tool does one thing, takes specific typed arguments, returns specific structured output). A good MCP server exposes many narrow tools rather than a few broad ones. This pattern is similar to Unix philosophy: small tools that do one thing well, can be composed.
Tool Descriptions as Model Interface
Tool descriptions are part of the model interface; they teach the model what the tool does and when to use it. A vague description (“query data”) creates bad planning (the model does not know if this tool can access sensitive data or only approved data) and unsafe calls (the model calls the tool with unsafe arguments). A precise description teaches the model: “query_support_tickets: Retrieve support tickets from the approved ticketing system. Arguments: status (open|closed|pending), assigned_to (user ID, optional), created_since (ISO 8601 date, optional). Returns: ticket ID, subject, status, created_date, assigned_to. Does NOT return customer PII or internal notes.” The description is the boundary between the model’s responsibility (decide to call the tool based on the goal) and the tool’s responsibility (validate and execute safely).
Tool Argument Validation and Authorization
The model proposes arguments to a tool; the tool validates them before executing. Validation includes: type checking (is the argument a valid user ID?), range checking (is the date in a reasonable range?), and authorization checking (is this agent authorized to query tickets for this user?). Never trust the model to enforce authorization. The pattern: model calls tool with arguments → tool validates arguments and checks authorization → if valid, execute; if not, return error with reason. The error message should be structured (error code, message, suggestion) so the loop can potentially retry or escalate intelligently.
Mutation and Idempotency
Mutating tools (create, update, delete) are higher-risk than read tools. Every mutating tool should support idempotency: the same request issued twice produces the same result as issuing it once, not double-applied changes. Idempotency is implemented via idempotency keys: the model (or harness) includes a unique ID with the request; the tool stores executed (idempotency_key, result) pairs; on duplicate requests, return the cached result. This prevents accidental duplicates due to retries or unexpected loop behavior. Example: “create_ticket: … idempotency_key (UUID, optional but recommended) … Returns: ticket ID, or existing ticket ID if this idempotency_key was already processed.”
Dry-Run and Staged Execution
High-impact mutating tools should support a dry-run mode: the tool validates the operation and returns what would be changed without actually executing. Example: “deploy_to_production (dry_run: true): Returns: list of services that would be updated, estimated downtime, rollback procedure. (dry_run: false): Executes the deployment and returns: deployment ID, status, completion time.” Dry-run lets the agent (and potentially a human) review the operation before it executes, reducing the risk of catastrophic mistakes.
Audit Logging and Tool Tracing
Every tool call must be logged: who called it (user ID or agent ID), when, with what arguments, what was the result, how long did it take, did it succeed or fail. Logs must be immutable (tamper-evident) and queryable (indexed by tool name, user, timestamp, result). Audit logs are essential for post-incident investigation: “which agent called delete_user and when?” Audit logs are also essential for security review: “has anyone called this sensitive tool recently? from unexpected locations?” Tool calls are the equivalent of database queries or API calls; they must be logged.
Separation of Read and Write Tools
Read-only tools (query, search, export-to-summary) are lower-risk; they cannot change state. Read tools can be broader in scope without proportional risk increase. Write tools (create, update, delete, publish, deploy) are higher-risk; they change state. Write tools must be scoped narrowly: “update_account_name” not “update_account.” High-impact write tools (delete_user, charge_payment, deploy_to_production) require human approval before execution. Approval is not something the harness hopes the model will request; it is enforced in the tool boundary: if human approval is required and has not been received, the tool returns an error asking for approval.
Per-Agent Credentials and Least Privilege
Each agent should have its own set of credentials (API keys, database users, AWS roles) scoped to exactly what it needs. Do not give all agents the same broad credentials “for simplicity.” An internal support agent should not have access to financial data. A data-analysis agent should not have production deployment credentials. Scope credentials at the tool boundary: the tool runs with its own scoped credentials, not inherited from the agent. The agent cannot escalate privileges; it can only use what the tool allows. This pattern is similar to least-privilege IAM: each service (or agent) has the minimum permissions needed.
Rate Limiting and Cost Control
Tools that are expensive (large language model calls, database queries, external API calls) should have rate limits: agents cannot invoke them more than N times per minute. Cost control: track the cost of each tool call (model token cost, API cost, compute cost) and sum it per agent, per user, per day. If cost exceeds a budget, stop executing expensive tools and escalate to an operator. This prevents a misbehaving loop from spending thousands of dollars unexpectedly.
Tool Design Checklist
| Property | Read-Only | Write (Low-Impact) | Write (High-Impact) |
|---|---|---|---|
| Scope | Can be broad (e.g., “query any table”) | Must be narrow (e.g., “update account status”) | Must be very narrow (e.g., “delete_user”) |
| Authorization | Tool validates access | Tool validates access | Tool validates access + requires human approval |
| Idempotency | Not needed | Recommended (idempotency key) | Required (idempotency key) |
| Dry-run | Not needed | Recommended | Required |
| Audit log | Required | Required | Required + human approval log |
| Rate limit | Optional (depends on cost) | Recommended | Required |
Common Pitfalls
- Pitfall: Exposing broad, shell-like tools because prototyping feels faster. Why: a shell tool gives the model unbounded power; a single reasoning error or prompt injection becomes unbounded damage. Fix: design tools narrowly; separate read from write; require human approval for high-impact operations.
- Pitfall: Treating a model response as a system boundary. Why: the model can be manipulated via prompt injection, jailbreaks, or simple misunderstanding; it is not an access-control layer. Fix: enforce authorization at the tool boundary, not by hoping the model respects instructions.
- Pitfall: Giving tools broad credentials because the prototype worked. Why: a prototype might work in a lab with a well-behaved model and no adversary; production requires narrow scopes and audit logs. Fix: give tools only the minimum credentials needed; require server-side validation of all arguments; log all tool calls; require human approval for high-impact actions.
- Pitfall: Shipping an agent without replayable traces and eval baselines. Why: without traces, it is impossible to debug failures or investigate unexpected behavior; without baselines, it is impossible to detect regression. Fix: log every tool call (input, output, decision, latency), version the tool definitions, and maintain an eval suite.
- Pitfall: Optimizing prompt cleverness before defining product risk and measurable quality. Why: clever prompts often appear to work in demos but fail on edge cases or under adversarial input. Fix: define what tools can do and what they cannot; define success metrics; build eval suites before optimizing prompts.