Connecting an AI agent to an MCP server is relatively easy.
Making that agent behave reliably is the harder part.
A production agent must decide:
- Which tools should be available?
- When should a tool be called?
- Which operations require approval?
- What context should be sent to the model?
- How should failures be handled?
- What should be logged?
- Can the workflow be redirected while it is running?
- How do we prevent the agent from taking actions outside the user’s permissions?
This is where concepts such as callbacks, hooks, tools, plugins, and steering become important.
These terms are sometimes used interchangeably, but they solve different problems.
The most important idea to understand is this:
MCP connects an agent to capabilities. The agent runtime controls how those capabilities are used.
MCP is an open standard for connecting AI applications with external data, tools, and workflows. MCP servers can expose capabilities to many compatible hosts instead of requiring a custom integration for every model and application.
However, MCP by itself is not the complete agent.
The complete system normally looks like this:
Let us break down each layer.
MCP Is the Capability Layer, Not the Entire Agent
The Model Context Protocol defines how an AI application communicates with external systems.
Its core server-side capabilities include:
- Resources
- Prompts
- Tools
MCP clients may also support features such as:
- Sampling
- Elicitation
- Roots
The protocol uses JSON-RPC messages and capability negotiation so clients and servers can determine which features each side supports.
A useful mental model is:
MCP does not standardize every internal behavior of your agent.
For example, MCP does not require your application to use a particular:
- Memory implementation
- Planning algorithm
- Retry strategy
- Callback system
- Agent framework
- Observability platform
- Policy engine
- Human-approval interface
Those belong to the host application or agent framework.
A Quick Comparison
| Concept | Main purpose | Usually belongs to | Can change execution? |
|---|---|---|---|
| Tool | Perform an operation | MCP server or agent runtime | Yes |
| Resource | Provide contextual data | MCP server | Indirectly |
| Prompt | Provide reusable instructions | MCP server or agent runtime | Yes |
| Callback | Observe an event | Agent framework | Usually no |
| Hook | Intercept a lifecycle stage | Agent framework | Yes |
| Middleware | Wrap one or more execution stages | Agent framework | Yes |
| Plugin | Package related capabilities | Framework or application | Yes |
| Steering | Direct runtime behavior | Entire agent system | Yes |
| Guardrail | Validate or block unsafe behavior | Agent runtime or policy layer | Yes |
The exact names vary between frameworks.
One framework may call something a callback, while another calls the same mechanism a hook or middleware function. Focus less on the label and more on what the mechanism is allowed to do.
1. Tools: What the Agent Can Do
A tool is an operation the model can request.
Examples include:
- Searching a database
- Reading a file
- Creating a support ticket
- Sending a message
- Updating a CRM record
- Running a calculation
- Triggering a deployment
- Requesting another specialized agent
In MCP, tools are exposed by servers and discovered by clients. Each tool has a name, description, input schema, and optionally an output schema. MCP tools are intended to be model-controlled, although the application may require user approval before execution.
For example:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("billing-service")
@mcp.tool()
def lookup_order(order_id: str) -> dict:
"""Retrieve an order and its current payment status."""
return {
"order_id": order_id,
"status": "delivered",
"amount": 79.99,
"refundable": True,
}
@mcp.tool()
def issue_refund(order_id: str, amount: float) -> dict:
"""Submit a refund for an eligible order."""
return {
"order_id": order_id,
"amount": amount,
"refund_status": "submitted",
}
def main() -> None:
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
The official Python MCP SDK uses Python type hints and docstrings to generate tool definitions and schemas automatically. MCP servers can run through local stdio connections or network transports such as Streamable HTTP.
A tool should be more than a Python function
An agent depends heavily on tool metadata.
Consider this description:
@mcp.tool()
def update(order_id: str, value: float):
"""Update an order."""
The model cannot easily determine:
- What is being updated?
- Is this operation reversible?
- Does
valuerepresent a price, refund, discount, or quantity? - Does the tool have side effects?
- Does the user need to approve it?
A stronger definition would be:
@mcp.tool()
def issue_refund(order_id: str, amount: float) -> dict:
"""
Submit a monetary refund for an eligible order.
Use this only after:
1. The order has been retrieved.
2. Refund eligibility has been verified.
3. The user has confirmed the refund amount.
Args:
order_id: Unique identifier of the order.
amount: Refund amount in US dollars.
"""
Tool descriptions are part of agent behavior design.
Poorly described tools create poorly behaved agents.
2. Resources: What the Agent Can Know
Resources provide contextual information.
Examples include:
- Policy documents
- Database schemas
- Customer profiles
- Configuration files
- Product catalogs
- Repository files
- Application state
- API documentation
MCP resources are identified using URIs and are designed to provide context that the host application can select, retrieve, or insert into the model’s context. The MCP specification describes resources as application-driven: the host decides how and when they are included.
Here is a simple resource:
@mcp.resource("policy://refunds")
def refund_policy() -> str:
"""Return the current refund policy."""
return """
Orders may be refunded within 30 days of delivery.
Refunds below $100 can be processed automatically after customer
confirmation. Refunds of $100 or more require supervisor approval.
"""
The agent can now retrieve authoritative policy information rather than relying entirely on model memory.
This creates an important separation:
Resource = information about the world
Tool = operation that changes the world
Reading a refund policy is a resource operation.
Submitting a refund is a tool operation.
Treating both as tools may work technically, but separating contextual data from actions often produces a clearer and safer architecture.
3. Prompts: Reusable Behavior Templates
MCP prompts allow servers to expose reusable prompt templates.
Prompts are generally user-controlled: a user or application explicitly selects a prompt and supplies its arguments. Clients can list available prompts and retrieve a completed prompt from the server.
For example:
@mcp.prompt()
def investigate_refund(order_id: str) -> str:
"""Create instructions for investigating a refund request."""
return f"""
Investigate refund eligibility for order {order_id}.
Follow this process:
1. Retrieve the order.
2. Read the refund policy.
3. Determine the maximum refundable amount.
4. Explain the result to the user.
5. Do not issue a refund until the user confirms.
"""
Prompts are useful when a domain expert owns a workflow.
Instead of embedding every domain instruction inside the agent application, an MCP server can expose prompts such as:
/investigate-refund
/review-pull-request
/analyze-incident
/prepare-customer-summary
/check-policy-compliance
This lets the capability provider package not only data and actions, but also recommended ways of using them.
However, prompts are not hard security controls.
A prompt can tell an agent not to issue an unauthorized refund. A policy hook must actually prevent the unauthorized tool call.
That difference matters:
Prompt instruction:
"Do not issue refunds over $100."
Policy enforcement:
Reject issue_refund when amount >= 100 unless approval exists.
Use prompts to guide behavior.
Use executable controls to enforce behavior.
4. Callbacks: Observing What Happened
A callback is a function invoked when an event occurs.
Typical callback events include:
- Agent started
- Model request started
- Model response received
- Tool selected
- Tool completed
- Agent handed work to another agent
- Run completed
- Run failed
Callbacks are commonly used for:
- Logging
- Metrics
- Tracing
- Cost tracking
- Notifications
- Audit records
- Debugging
- Evaluation data collection
For example:
async def on_tool_completed(tool_name: str, result: object) -> None:
audit_log.write({
"event": "tool_completed",
"tool": tool_name,
"result_type": type(result).__name__,
})
The callback observes the result.
It does not necessarily decide whether the tool should have run.
The OpenAI Agents SDK, for example, provides lifecycle handlers around agent, model, tool, and handoff events. It supports run-level hooks for observing the complete workflow and agent-level hooks attached to a particular agent.
A simplified example looks like this:
from agents import RunHooks
class AuditHooks(RunHooks):
async def on_agent_start(self, context, agent):
print(f"Agent started: {agent.name}")
async def on_tool_start(self, context, agent, tool):
print(f"Calling tool: {tool.name}")
async def on_tool_end(self, context, agent, tool, result):
print(f"Tool completed: {tool.name}")
async def on_agent_end(self, context, agent, output):
print(f"Agent completed: {agent.name}")
Do not put all business logic in callbacks
Callbacks are excellent for side effects such as telemetry.
They become dangerous when critical workflow behavior depends on them.
For example, avoid making a logging callback responsible for refund authorization:
async def on_tool_start(...):
# Bad architectural boundary
if tool.name == "issue_refund":
perform_authorization()
A dedicated policy hook, tool wrapper, or approval system is usually easier to test and reason about.
5. Hooks: Intercepting the Agent Lifecycle
Hooks are interception points.
A hook can run:
- Before the agent starts
- Before a model request
- After a model response
- Before a tool call
- After a tool call
- Around the entire tool call
- Before a handoff
- Before the final response
- After the run finishes
Unlike a purely observational callback, a hook may modify, block, retry, redirect, or replace an operation.
For example:
async def before_tool_call(tool_name: str, arguments: dict, context):
if tool_name == "issue_refund":
amount = arguments["amount"]
if amount >= 100 and not context.supervisor_approved:
raise PermissionError(
"Supervisor approval is required for refunds of $100 or more."
)
return arguments
This hook changes the behavior of the system by preventing an unauthorized action.
LangChain’s current middleware model illustrates two common hook styles:
- Node-style hooks, which run at specific lifecycle points.
- Wrap-style hooks, which execute around model or tool calls and can control the operation itself.
Common hook patterns
Input-validation hook
async def before_agent(user_input: str) -> str:
if not user_input.strip():
raise ValueError("Input cannot be empty.")
return user_input
Context-injection hook
async def before_model(request, context):
request.system_prompt += (
f"\nThe current user role is: {context.user_role}."
)
return request
Tool-authorization hook
async def before_tool(tool_name, arguments, context):
if tool_name not in context.allowed_tools:
raise PermissionError(f"Tool not permitted: {tool_name}")
return arguments
Retry hook
async def around_tool(call_tool, tool_name, arguments):
for attempt in range(3):
try:
return await call_tool(tool_name, arguments)
except TimeoutError:
if attempt == 2:
raise
Output-normalization hook
async def after_tool(tool_name, result):
return {
"tool": tool_name,
"success": True,
"data": result,
}
Hooks are one of the best places to implement deterministic runtime controls.
6. Middleware: Composing Multiple Hooks
Middleware is a reusable layer that wraps part of the agent execution pipeline.
A middleware component might contain several hooks:
SecurityMiddleware
├── before_model
├── before_tool
└── after_tool
ObservabilityMiddleware
├── before_agent
├── after_model
└── after_agent
CostControlMiddleware
├── before_model
└── after_model
Middleware is useful for cross-cutting concerns such as:
- Authentication
- Authorization
- PII filtering
- Rate limiting
- Prompt transformation
- Tool filtering
- Retry policies
- Context compression
- Model routing
- Caching
- Human approval
- Observability
Modern agent frameworks use middleware to dynamically transform prompts, select tools, manage context, add guardrails, and apply fallback behavior.
A conceptual pipeline could look like this:
User Request
│
▼
Authentication Middleware
│
▼
Input Safety Middleware
│
▼
Context Construction Middleware
│
▼
Model Call
│
▼
Tool Policy Middleware
│
▼
MCP Tool Call
│
▼
Output Validation Middleware
│
▼
Final Response
The order matters.
For example, authorization should normally happen before a sensitive tool executes, not after it completes.
7. Plugins: Packaging Related Capabilities
“Plugin” is not a primary MCP protocol primitive.
It is usually a framework or application-level concept.
A plugin generally packages a group of related capabilities, such as:
Billing Plugin
├── lookup_order
├── calculate_refund
├── issue_refund
├── refund policy resource
└── investigate-refund prompt
Semantic Kernel defines a plugin as a group of functions that can be exposed to AI applications. It can import capabilities from native code, OpenAPI specifications, or MCP servers.
Depending on the framework, a plugin might contain:
- Tools
- Prompt templates
- Resources
- Authentication logic
- Configuration
- Dependencies
- Lifecycle hooks
- UI metadata
- Version information
An MCP server can therefore act like a portable plugin boundary.
But the terms are not identical:
MCP server = Protocol endpoint exposing standardized capabilities
Plugin = Logical package used by a framework or application
One plugin may connect to several MCP servers.
One MCP server may expose capabilities that are imported as a plugin.
Why plugins are useful
Without plugins, an agent configuration may become a long list of unrelated tools:
tools = [
lookup_order,
issue_refund,
get_customer,
search_documents,
create_ticket,
send_email,
update_crm,
run_query,
]
With plugins or namespaces, the structure is clearer:
billing.lookup_order
billing.issue_refund
support.create_ticket
support.search_articles
communication.send_email
crm.get_customer
crm.update_customer
Grouping related tools can also make capability discovery easier for the model.
8. Steering: Controlling the Agent’s Direction
Steering is the broadest concept in this article.
It is not one specific MCP message or API.
Steering means influencing what the agent does next.
Examples include:
- Changing its instructions
- Adding contextual information
- Removing tools
- Requiring a specific tool
- Blocking a tool
- Routing to another model
- Routing to another agent
- Asking the user for clarification
- Requiring approval
- Stopping the workflow
- Changing output structure
- Reducing the remaining budget
- Switching from action mode to read-only mode
Steering can happen before or during execution.
Prompt steering
Change the instructions given to the model:
def build_instructions(context) -> str:
if context.mode == "read_only":
return """
Investigate the request using read-only tools.
Do not call tools that modify external systems.
"""
return """
Resolve the request using available tools.
Confirm with the user before performing irreversible actions.
"""
Dynamic instructions are supported by agent frameworks such as the OpenAI Agents SDK, where the instruction function can use runtime context to construct the system prompt.
Prompt steering is flexible but probabilistic.
The model may misunderstand an instruction.
Therefore, prompt steering should be paired with capability and policy controls.
Context steering
Change the information visible to the model:
User asks for refund
│
├── Include order details
├── Include refund policy
├── Include previous support interactions
└── Exclude unrelated customer records
Context steering is often more effective than simply adding more instructions.
The model’s behavior depends on the information available at the time it makes a decision.
MCP resources are especially useful here because the application can retrieve relevant data from external systems and selectively place it into the model context.
Capability steering
Change which tools the agent can see:
def available_tools(context) -> set[str]:
tools = {
"lookup_order",
"read_refund_policy",
}
if context.user_confirmed:
tools.add("issue_refund")
if context.is_supervisor:
tools.add("override_refund_limit")
return tools
This is stronger than writing:
Do not use issue_refund before confirmation.
When the tool is hidden, the model cannot select it.
Agent runtimes can support static or dynamic MCP tool filtering. For example, the OpenAI Agents SDK allows an MCP tool filter to decide which tools are exposed based on the active agent and run context.
Tool-choice steering
Sometimes you may want to control whether tools are used:
auto = Model decides whether to call a tool
required = Model must call at least one tool
none = Model cannot call a tool
specific = Model must call a named tool
This can be useful for deterministic workflow stages.
For example:
Stage 1: Force lookup_order
Stage 2: Let the model analyze the order
Stage 3: Require user confirmation
Stage 4: Permit issue_refund
Policy steering
Policy steering applies deterministic rules:
def evaluate_tool_call(tool_name: str, arguments: dict, context):
if tool_name == "issue_refund":
if not context.user_confirmed:
return "DENY", "Customer confirmation is missing."
if arguments["amount"] >= 100 and not context.supervisor_approved:
return "DEFER", "Supervisor approval is required."
return "ALLOW", None
A useful policy engine may return more than True or False:
ALLOW = Execute the tool
DENY = Block the tool
MODIFY = Change the arguments
DEFER = Request approval
RETRY = Try again
ROUTE = Send to another agent
Human steering
Sometimes the agent does not have enough information to continue safely.
MCP elicitation allows a server to request additional user information through the client. Current MCP elicitation supports structured form interactions and URL-based flows for sensitive interactions.
For example:
Agent: A refund can be issued, but I need confirmation.
Requested information:
- Confirm refund amount: $79.99
- Refund destination: Original payment method
Actions:
[Confirm] [Modify] [Cancel]
Human steering is especially valuable for:
- Financial transactions
- External communication
- Production changes
- Record deletion
- Account changes
- Decisions with legal or compliance impact
9. How MCP Features Support Steering
MCP does not provide a single steer_agent() operation.
Instead, several MCP capabilities can contribute to steering.
Resources steer through context
policy://refunds
customer://123/profile
order://A1004
schema://analytics
Resources determine what factual and operational context is available.
Prompts steer through instructions
/investigate-refund
/review-security-incident
/prepare-release-notes
Prompts provide reusable behavior templates.
Tools steer through affordances
The available tools tell the model what actions are possible.
An agent with only these tools:
search_policy
lookup_order
will behave differently from an agent with:
search_policy
lookup_order
issue_refund
delete_order
send_email
Elicitation steers through user input
The server can pause an interactive workflow and request information or approval from the user.
Sampling steers through nested model calls
MCP sampling allows a server to request an LLM generation through the client. This lets the client retain control over model selection, access, and permissions while enabling more agentic server workflows. Current MCP sampling can also support tool-enabled sampling when the client declares the required capability.
For example, an MCP server might request a model to:
- Summarize retrieved records
- Classify a document
- Select relevant information
- Generate a draft
- Analyze tool output
The client still controls whether the sampling request is allowed.
10. Putting Everything Together
Let us build a simplified refund agent.
MCP server
# billing_server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("billing")
@mcp.resource("policy://refunds")
def refund_policy() -> str:
"""Return the organization's refund rules."""
return """
Refunds are available within 30 days of delivery.
Refunds below $100 require customer confirmation.
Refunds of $100 or more require customer confirmation
and supervisor approval.
"""
@mcp.prompt()
def investigate_refund(order_id: str) -> str:
"""Generate a refund investigation workflow."""
return f"""
Investigate order {order_id}.
Retrieve the order, consult the refund policy, explain
eligibility, and ask for confirmation.
Do not issue the refund without confirmation.
"""
@mcp.tool()
def lookup_order(order_id: str) -> dict:
"""Retrieve an order and determine its basic refund eligibility."""
return {
"order_id": order_id,
"delivery_age_days": 12,
"amount": 79.99,
"status": "delivered",
}
@mcp.tool()
def issue_refund(order_id: str, amount: float) -> dict:
"""Issue a confirmed refund for an eligible order."""
return {
"order_id": order_id,
"amount": amount,
"status": "submitted",
}
def main() -> None:
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
Agent runtime
The following example uses the OpenAI Agents SDK as one possible host runtime. The same architecture can be implemented with other frameworks.
# agent.py
import asyncio
from dataclasses import dataclass
from agents import Agent, RunContextWrapper, RunHooks, Runner
from agents.mcp import (
MCPServerStdio,
ToolFilterContext,
)
@dataclass
class RefundContext:
user_id: str
user_confirmed: bool = False
supervisor_approved: bool = False
def dynamic_instructions(
wrapper: RunContextWrapper[RefundContext],
agent: Agent[RefundContext],
) -> str:
context = wrapper.context
approval_status = (
"Supervisor approval is available."
if context.supervisor_approved
else "Supervisor approval is not available."
)
return f"""
You are a refund-support agent.
Investigate requests before taking action.
Explain refund eligibility clearly.
Never issue a refund without customer confirmation.
{approval_status}
"""
async def filter_tools(
filter_context: ToolFilterContext,
tool,
) -> bool:
run_context = filter_context.run_context.context
# Read-only investigation tools remain available.
if tool.name != "issue_refund":
return True
# Hide the action tool until confirmation is recorded.
return bool(run_context.user_confirmed)
class AuditHooks(RunHooks):
async def on_agent_start(self, context, agent):
print(f"[audit] agent_started={agent.name}")
async def on_tool_start(self, context, agent, tool):
print(f"[audit] tool_started={tool.name}")
async def on_tool_end(self, context, agent, tool, result):
print(f"[audit] tool_completed={tool.name}")
async def on_agent_end(self, context, agent, output):
print(f"[audit] agent_completed={agent.name}")
async def main() -> None:
refund_context = RefundContext(
user_id="customer-123",
user_confirmed=False,
supervisor_approved=False,
)
async with MCPServerStdio(
name="Billing MCP",
params={
"command": "python",
"args": ["billing_server.py"],
},
cache_tools_list=True,
tool_filter=filter_tools,
require_approval={
"always": {
"tool_names": ["issue_refund"],
}
},
) as billing_server:
agent = Agent[RefundContext](
name="Refund Assistant",
instructions=dynamic_instructions,
mcp_servers=[billing_server],
)
result = await Runner.run(
agent,
"Can I get a refund for order A1004?",
context=refund_context,
hooks=AuditHooks(),
)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
The runtime provides several layers of behavioral control:
Dynamic instructions
└── Tell the model how to behave
Tool filter
└── Hides issue_refund before confirmation
Approval policy
└── Requires approval when issue_refund is requested
Audit hooks
└── Record what happened
MCP server
└── Provides the policy, workflow prompt, and billing tools
The OpenAI Agents SDK currently supports local MCP servers through stdio, Streamable HTTP, and legacy SSE integrations. It also provides tool filtering, approval policies, MCP prompt retrieval, caching, metadata injection, and tracing around MCP activity.
11. Callbacks vs. Hooks: A Practical Rule
The boundary can be summarized with one question:
Is this code only observing the event, or can it influence the event?
Use a callback when you need to:
- Record a metric
- Create a trace
- Send an internal notification
- Store evaluation data
- Measure latency
- Track token usage
Use a hook or middleware layer when you need to:
- Change the prompt
- Add context
- Validate arguments
- Remove a tool
- Block an operation
- Retry a request
- Route to another model
- Require human approval
- Change the result
- Stop the agent
A callback says:
The agent called issue_refund.
A hook says:
The agent is trying to call issue_refund.
Should this call be allowed?
12. Production Design Recommendations
Keep MCP servers focused
Avoid creating one enormous MCP server containing every organizational capability.
Prefer domain boundaries such as:
billing-mcp
customer-support-mcp
github-mcp
analytics-mcp
policy-mcp
communications-mcp
Focused servers are easier to:
- Secure
- Test
- Version
- Monitor
- Assign ownership
- Apply least-privilege access
Expose the smallest necessary toolset
Too many tools can create several problems:
- Larger model context
- Confusing tool selection
- Increased latency
- Name collisions
- Greater security exposure
- More opportunities for incorrect actions
Dynamically expose tools based on:
- User permissions
- Active workflow stage
- Tenant
- Environment
- Agent role
- Feature flags
- Authentication status
Separate read tools from write tools
For example:
Read-only:
- lookup_order
- get_customer
- search_policy
State-changing:
- issue_refund
- update_customer
- send_email
- delete_record
Write operations should receive stronger validation, authorization, and approval requirements.
Treat tool output as untrusted input
An MCP tool may return:
- Malformed data
- Unexpected instructions
- Excessively large content
- Sensitive information
- Stale records
- Errors disguised as success
- Content that attempts to influence the model
Validate and normalize tool results before returning them to the model.
Put authorization outside the model
Do not ask the model to decide whether the current user has permission.
Instead, calculate permissions using trusted application code:
allowed = authorization_service.can_execute(
user_id=context.user_id,
action="issue_refund",
resource_id=order_id,
)
The model may help identify the requested action.
The policy system should decide whether the action is allowed.
Make side effects explicit
Tool descriptions should clearly indicate whether an operation:
- Reads data
- Writes data
- Sends communication
- Deletes information
- Charges money
- Changes permissions
- Triggers another workflow
This improves both model selection and human review.
Add idempotency
An agent may retry a tool because of:
- Network timeouts
- Model retries
- Application restarts
- Lost responses
- Workflow recovery
A payment or refund tool should support an idempotency key:
issue_refund(
order_id="A1004",
amount=79.99,
idempotency_key="refund-A1004-session-987",
)
This prevents accidental duplicate operations.
Trace decisions, not only tool calls
A useful trace should answer:
What did the user request?
What instructions were active?
What resources were included?
Which tools were visible?
Which tool did the model select?
What arguments were proposed?
Which policy was evaluated?
Was approval requested?
What result was returned?
What did the agent tell the user?
The OpenAI Agents SDK’s tracing system, for example, records model generations, tool calls, handoffs, guardrails, and custom workflow events.
13. The Final Mental Model
The behavior of an MCP-powered agent is not controlled by one prompt.
It emerges from several layers working together:
Instructions
Tell the agent what it should do.
Resources
Give the agent relevant knowledge.
Tools
Define what the agent can do.
Plugins
Package related capabilities.
Callbacks
Record what the agent did.
Hooks
Intercept what the agent is about to do.
Middleware
Applies reusable behavior across the lifecycle.
Steering
Changes the agent's direction at runtime.
Guardrails and policies
Define what the agent is allowed to do.
MCP
Standardizes how external capabilities are connected.
A reliable agent does not depend entirely on the model making the right decision.
It combines probabilistic reasoning with deterministic controls.
That is the difference between a demo agent and a production agent.
A demo agent has a prompt and a few tools.
A production agent has:
- Carefully scoped MCP capabilities
- Dynamic context
- Runtime steering
- Tool filtering
- Authorization
- Human approval
- Lifecycle hooks
- Error handling
- Tracing
- Evaluations
- Auditable policies
MCP gives the agent access to the outside world.
Callbacks help us understand what happened.
Hooks let us intervene.
Plugins keep capabilities organized.
Tools let the agent act.
Steering ensures that the agent moves in the right direction.
And production engineering ensures that it does so safely, reliably, and within the user’s authority.


Top comments (0)