MCP Tool Annotations Module
MCP 2025-11-25 tool annotation hints for behavioral metadata
MCP Tool Annotations Module
Tool annotations provide behavioral hints to MCP clients about how tools behave. This follows the MCP 2025-11-25 specification.
Annotation Hints
| Hint | Type | Default | Description |
|---|---|---|---|
readOnlyHint | boolean | false | Tool only reads data, doesn’t modify state |
destructiveHint | boolean | true | Tool may have destructive/irreversible effects |
idempotentHint | boolean | false | Tool can be called multiple times with same result |
openWorldHint | boolean | true | Tool interacts with external world (network, APIs) |
Code Usage
Default Annotations
from praisonai.mcp_server.registry import MCPToolDefinition
# Tool with default annotations
tool = MCPToolDefinition(
name="example.tool",
description="Example tool",
handler=lambda: "result",
input_schema={"type": "object"}
)
# Check defaults
print(tool.read_only_hint) # False
print(tool.destructive_hint) # True
print(tool.idempotent_hint) # False
print(tool.open_world_hint) # TrueRead-Only Tool
# Tool that only reads data
read_tool = MCPToolDefinition(
name="memory.show",
description="Show memory contents without modification",
handler=show_memory,
input_schema={"type": "object"},
read_only_hint=True,
destructive_hint=False,
)Destructive Tool
# Tool that deletes data
delete_tool = MCPToolDefinition(
name="file.delete",
description="Delete a file permanently",
handler=delete_file,
input_schema={
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path"}
},
"required": ["path"]
},
destructive_hint=True,
idempotent_hint=False, # Deleting twice has different effects
)Idempotent Tool
# Tool that can be safely retried
config_tool = MCPToolDefinition(
name="config.set",
description="Set configuration value",
handler=set_config,
input_schema={
"type": "object",
"properties": {
"key": {"type": "string"},
"value": {"type": "string"}
}
},
idempotent_hint=True,
destructive_hint=False,
)Closed-World Tool
# Tool that doesn't interact with external systems
session_tool = MCPToolDefinition(
name="session.get",
description="Get session data (internal only)",
handler=get_session,
input_schema={"type": "object"},
read_only_hint=True,
destructive_hint=False,
open_world_hint=False, # No external interaction
)Tool Schema Output
Annotations are included in the MCP schema:
tool = MCPToolDefinition(
name="example.tool",
description="Example",
handler=lambda: None,
input_schema={"type": "object"},
read_only_hint=True,
destructive_hint=False,
)
schema = tool.to_mcp_schema()
print(schema)Output:
{
"name": "example.tool",
"description": "Example",
"inputSchema": {"type": "object"},
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true
}
}Custom Title Annotation
tool = MCPToolDefinition(
name="praisonai.workflow.run",
description="Execute a PraisonAI workflow",
handler=run_workflow,
input_schema={"type": "object"},
title="Run AI Workflow", # Human-friendly title
)
schema = tool.to_mcp_schema()
print(schema["annotations"]["title"]) # "Run AI Workflow"Category and Tags
tool = MCPToolDefinition(
name="web.search",
description="Search the web",
handler=web_search,
input_schema={"type": "object"},
category="web",
tags=["search", "internet", "query"],
read_only_hint=True,
)
print(tool.category) # "web"
print(tool.tags) # ["search", "internet", "query"]Automatic Hint Inference
The registry bridge can infer hints from tool names:
from praisonai.mcp_server.adapters.tools_bridge import _infer_tool_hints
# Read-only patterns: show, list, get, read, search, find, query
hints = _infer_tool_hints("memory.show")
print(hints["read_only_hint"]) # True
# Idempotent patterns: set, update, configure
hints = _infer_tool_hints("config.set")
print(hints["idempotent_hint"]) # True
# Closed-world patterns: memory, session, config, local
hints = _infer_tool_hints("session.create")
print(hints["open_world_hint"]) # FalseSearching by Annotations
from praisonai.mcp_server.registry import MCPToolRegistry
registry = MCPToolRegistry()
# ... register tools ...
# Find all read-only tools
results, _, total = registry.search(read_only=True)
print(f"Found {total} read-only tools")
# Find tools by category
results, _, total = registry.search(category="memory")Best Practices
- Set
readOnlyHint=Truefor tools that only retrieve data - Set
destructiveHint=Truefor tools that delete or modify data irreversibly - Set
idempotentHint=Truefor tools safe to retry on failure - Set
openWorldHint=Falsefor internal/local-only tools
See Also
- MCP Tool Annotations CLI – CLI commands for tool info
- MCP Tool Search – Search tools by annotations
- MCP Server – Core MCP server documentation