"""04: Custom tools via an in-process MCP server."""
import asyncio
from typing import Annotated, Any
from claude_agent_sdk import (
AssistantMessage,
ClaudeAgentOptions,
ResultMessage,
SystemMessage,
ToolAnnotations,
ToolUseBlock,
UserMessage,
ToolResultBlock,
create_sdk_mcp_server,
query,
tool,
)
from demo_util import PROJECT
INVENTORY = {"widget": 42, "gadget": 0, "sprocket": 7}
@tool(
"get_stock",
"Look up how many units of a product are in stock",
{"product": Annotated[str, "Product name, lowercase"]},
annotations=ToolAnnotations(readOnlyHint=True), # safe to run in parallel
)
async def get_stock(args: dict[str, Any]) -> dict[str, Any]:
product = args["product"].lower().rstrip("s") # accept "widgets" for "widget"
if product not in INVENTORY:
return {
"content": [{"type": "text", "text": f"Unknown product: {product}"}],
"is_error": True,
}
return {"content": [{"type": "text", "text": f"{product}: {INVENTORY[product]} units"}]}
@tool(
"reorder",
"Place a reorder for a product",
{
"type": "object",
"properties": {
"product": {"type": "string"},
"quantity": {"type": "integer", "minimum": 1, "maximum": 1000},
"priority": {"type": "string", "enum": ["normal", "rush"]},
},
"required": ["product", "quantity"], # priority is optional
},
)
async def reorder(args: dict[str, Any]) -> dict[str, Any]:
priority = args.get("priority", "normal")
INVENTORY[args["product"]] = INVENTORY.get(args["product"], 0) + args["quantity"]
return {"content": [{"type": "text", "text":
f"Reorder placed: {args['quantity']} x {args['product']} ({priority}). "
f"New stock: {INVENTORY[args['product']]}"}]}
inventory_server = create_sdk_mcp_server(name="inventory", version="1.0.0", tools=[get_stock, reorder])
async def main():
options = ClaudeAgentOptions(
cwd=PROJECT,
mcp_servers={"inventory": inventory_server},
allowed_tools=["mcp__inventory__*"], # wildcard: every tool on this server
tools=[], # no built-in tools at all
max_turns=8,
)
async for message in query(
prompt="Check stock for widgets, gadgets and doohickeys. "
"If anything is out of stock, place a rush reorder for 25 units.",
options=options,
):
if isinstance(message, SystemMessage) and message.subtype == "init":
mcp = [t for t in message.data.get("tools", []) if t.startswith("mcp__")]
print("MCP tools visible at init:", mcp)
print("MCP servers:", message.data.get("mcp_servers"))
elif isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
print(f"[tool call] {block.name}({block.input})")
elif isinstance(message, UserMessage) and isinstance(message.content, list):
for block in message.content:
if isinstance(block, ToolResultBlock):
print(f"[tool result]{' ERROR' if block.is_error else ''} {block.content}")
elif isinstance(message, ResultMessage) and message.subtype == "success":
print("\nFinal answer:", message.result)
print("\nInventory after run:", INVENTORY)
asyncio.run(main())
96 lines · Back to the folder