Tools, Resources, and Prompts
Advanced Tool Patterns
The patterns below all answer the same question in different ways: how much work should happen inside one tool call, and how much should be left to the model to orchestrate?
Tool Composition
Two tools that are always used together are often better as one. Each round trip costs a turn, and every turn is a chance for the model to lose the thread:
Two granular tools vs one composed tool
Composition is not free. A composed tool hides its intermediate steps, so when the summary is wrong you cannot tell whether the search or the summariser failed. The rule of thumb: compose when the intermediate result is never independently useful, and keep them separate when the model might legitimately want to stop halfway.
@server.list_tools()
async def list_tools():
return [
Tool(
name="search_and_summarize",
description="Search documents and return a summary",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "integer", "default": 5}
},
"required": ["query"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "search_and_summarize":
# Step 1: Search
results = await search_documents(arguments["query"])
# Step 2: Summarize
summary = await generate_summary(results[:arguments.get("max_results", 5)])
return [TextContent(type="text", text=summary)]
Streaming Results
For long-running operations, stream results:
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "analyze_large_dataset":
results = []
async for batch in process_in_batches(arguments["data"]):
results.append(batch)
# Yield intermediate results
yield [TextContent(type="text", text=f"Processed batch: {len(results)}")]
# Final result
yield [TextContent(type="text", text=json.dumps(results))]
Confirmable Actions
For dangerous operations, require confirmation:
Tool(
name="delete_all_files",
description="Delete all files in a directory. DESTRUCTIVE ACTION.",
inputSchema={
"type": "object",
"properties": {
"directory": {"type": "string"},
"confirm": {
"type": "boolean",
"description": "Must be true to proceed"
}
},
"required": ["directory", "confirm"]
}
)
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "delete_all_files":
if not arguments.get("confirm"):
return [TextContent(
type="text",
text="Action not confirmed. Set confirm=true to proceed."
)]
# Proceed with deletion
await delete_files(arguments["directory"])
Tool Dependencies
Build tools that depend on other tools:
class ToolRegistry:
def __init__(self):
self.tools = {}
def register(self, name, handler):
self.tools[name] = handler
async def call(self, name, arguments):
return await self.tools[name](arguments)
registry = ToolRegistry()
async def get_user_handler(args):
return await db.get_user(args["id"])
async def get_user_orders_handler(args):
# Depends on get_user
user = await registry.call("get_user", {"id": args["user_id"]})
return await db.get_orders(user["id"])
registry.register("get_user", get_user_handler)
registry.register("get_user_orders", get_user_orders_handler)
Next, we'll explore advanced resource patterns. :::
Sign in to rate