tool_annotations_example.py
1 #!/usr/bin/env python3 2 """ 3 MCP Tool Annotations Example 4 5 Demonstrates the MCP 2025-11-25 tool annotation hints: 6 - readOnlyHint: Tool only reads data, doesn't modify 7 - destructiveHint: Tool may have destructive side effects 8 - idempotentHint: Tool can be called multiple times with same result 9 - openWorldHint: Tool interacts with external world 10 11 Usage: 12 python tool_annotations_example.py 13 """ 14 15 import json 16 from praisonai.mcp_server.registry import MCPToolDefinition, MCPToolRegistry 17 18 19 def demo_default_annotations(): 20 """Demonstrate default annotation values.""" 21 print("\n" + "=" * 60) 22 print("Default Annotations Demo") 23 print("=" * 60) 24 25 tool = MCPToolDefinition( 26 name="example.default", 27 description="Tool with default annotations", 28 handler=lambda: None, 29 input_schema={"type": "object"}, 30 ) 31 32 print("\nDefault annotation values (per MCP 2025-11-25 spec):") 33 print(f" readOnlyHint: {tool.read_only_hint} (default: False)") 34 print(f" destructiveHint: {tool.destructive_hint} (default: True)") 35 print(f" idempotentHint: {tool.idempotent_hint} (default: False)") 36 print(f" openWorldHint: {tool.open_world_hint} (default: True)") 37 38 39 def demo_read_only_tool(): 40 """Demonstrate a read-only tool.""" 41 print("\n" + "=" * 60) 42 print("Read-Only Tool Demo") 43 print("=" * 60) 44 45 tool = MCPToolDefinition( 46 name="memory.show", 47 description="Show memory contents without modification", 48 handler=lambda: {"memory": "contents"}, 49 input_schema={"type": "object"}, 50 read_only_hint=True, 51 destructive_hint=False, 52 ) 53 54 schema = tool.to_mcp_schema() 55 print("\nRead-only tool schema:") 56 print(json.dumps(schema, indent=2)) 57 58 59 def demo_destructive_tool(): 60 """Demonstrate a destructive tool.""" 61 print("\n" + "=" * 60) 62 print("Destructive Tool Demo") 63 print("=" * 60) 64 65 tool = MCPToolDefinition( 66 name="file.delete", 67 description="Delete a file permanently", 68 handler=lambda path: f"Deleted {path}", 69 input_schema={ 70 "type": "object", 71 "properties": { 72 "path": {"type": "string", "description": "File path to delete"} 73 }, 74 "required": ["path"] 75 }, 76 destructive_hint=True, 77 idempotent_hint=False, # Deleting twice has different effects 78 ) 79 80 schema = tool.to_mcp_schema() 81 print("\nDestructive tool schema:") 82 print(json.dumps(schema, indent=2)) 83 84 85 def demo_idempotent_tool(): 86 """Demonstrate an idempotent tool.""" 87 print("\n" + "=" * 60) 88 print("Idempotent Tool Demo") 89 print("=" * 60) 90 91 tool = MCPToolDefinition( 92 name="config.set", 93 description="Set a configuration value (can be called multiple times safely)", 94 handler=lambda key, value: f"Set {key}={value}", 95 input_schema={ 96 "type": "object", 97 "properties": { 98 "key": {"type": "string"}, 99 "value": {"type": "string"} 100 }, 101 "required": ["key", "value"] 102 }, 103 idempotent_hint=True, 104 destructive_hint=False, 105 ) 106 107 schema = tool.to_mcp_schema() 108 print("\nIdempotent tool schema:") 109 print(json.dumps(schema, indent=2)) 110 111 112 def demo_closed_world_tool(): 113 """Demonstrate a closed-world tool (openWorldHint=False).""" 114 print("\n" + "=" * 60) 115 print("Closed-World Tool Demo") 116 print("=" * 60) 117 118 tool = MCPToolDefinition( 119 name="session.get", 120 description="Get session data (internal only, no external interaction)", 121 handler=lambda session_id: {"session": session_id}, 122 input_schema={ 123 "type": "object", 124 "properties": { 125 "session_id": {"type": "string"} 126 } 127 }, 128 read_only_hint=True, 129 destructive_hint=False, 130 open_world_hint=False, # Internal tool, no external interaction 131 ) 132 133 schema = tool.to_mcp_schema() 134 print("\nClosed-world tool schema:") 135 print(json.dumps(schema, indent=2)) 136 137 138 def demo_tool_with_title(): 139 """Demonstrate tool with custom title annotation.""" 140 print("\n" + "=" * 60) 141 print("Tool with Title Demo") 142 print("=" * 60) 143 144 tool = MCPToolDefinition( 145 name="praisonai.workflow.run", 146 description="Execute a PraisonAI workflow from YAML definition", 147 handler=lambda workflow: "Workflow executed", 148 input_schema={"type": "object"}, 149 title="Run AI Workflow", # Human-friendly title 150 category="workflow", 151 tags=["ai", "automation", "workflow"], 152 ) 153 154 schema = tool.to_mcp_schema() 155 print("\nTool with title:") 156 print(json.dumps(schema, indent=2)) 157 print(f"\nCategory: {tool.category}") 158 print(f"Tags: {tool.tags}") 159 160 161 def demo_registry_with_annotations(): 162 """Demonstrate registry with annotated tools.""" 163 print("\n" + "=" * 60) 164 print("Registry with Annotated Tools Demo") 165 print("=" * 60) 166 167 registry = MCPToolRegistry() 168 169 # Register tools with different annotation profiles 170 registry._tools["read.data"] = MCPToolDefinition( 171 name="read.data", 172 description="Read data", 173 handler=lambda: None, 174 input_schema={"type": "object"}, 175 read_only_hint=True, 176 destructive_hint=False, 177 ) 178 179 registry._tools["write.data"] = MCPToolDefinition( 180 name="write.data", 181 description="Write data", 182 handler=lambda: None, 183 input_schema={"type": "object"}, 184 read_only_hint=False, 185 destructive_hint=True, 186 ) 187 188 # Search for read-only tools 189 results, _, total = registry.search(read_only=True) 190 print(f"\nRead-only tools ({total}):") 191 for tool in results: 192 print(f" - {tool['name']}") 193 194 # Search for destructive tools 195 results, _, total = registry.search(read_only=False) 196 print(f"\nNon-read-only tools ({total}):") 197 for tool in results: 198 print(f" - {tool['name']}") 199 200 201 def main(): 202 """Run all demos.""" 203 print("\n" + "#" * 60) 204 print("# MCP Tool Annotations Examples") 205 print("# MCP Protocol Version: 2025-11-25") 206 print("#" * 60) 207 208 demo_default_annotations() 209 demo_read_only_tool() 210 demo_destructive_tool() 211 demo_idempotent_tool() 212 demo_closed_world_tool() 213 demo_tool_with_title() 214 demo_registry_with_annotations() 215 216 print("\n" + "=" * 60) 217 print("All demos completed!") 218 print("=" * 60 + "\n") 219 220 221 if __name__ == "__main__": 222 main()