Skip to main content

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

HintTypeDefaultDescription
readOnlyHintbooleanfalseTool only reads data, doesn’t modify state
destructiveHintbooleantrueTool may have destructive/irreversible effects
idempotentHintbooleanfalseTool can be called multiple times with same result
openWorldHintbooleantrueTool 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)     # True

Read-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"])  # False

Searching 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

  1. Set readOnlyHint=True for tools that only retrieve data
  2. Set destructiveHint=True for tools that delete or modify data irreversibly
  3. Set idempotentHint=True for tools safe to retry on failure
  4. Set openWorldHint=False for internal/local-only tools

See Also