/ examples / mcp / tool_annotations_example.py
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()