Set read-only annotation hints on each tool for security
This commit is contained in:
@@ -14,9 +14,9 @@ import os
|
||||
# These values are used in server responses and for tracking releases
|
||||
# IMPORTANT: This is the single source of truth for version and author info
|
||||
# Semantic versioning: MAJOR.MINOR.PATCH
|
||||
__version__ = "5.7.0"
|
||||
__version__ = "5.7.1"
|
||||
# Last update date in ISO format
|
||||
__updated__ = "2025-06-23"
|
||||
__updated__ = "2025-06-26"
|
||||
# Primary maintainer
|
||||
__author__ = "Fahad Gilani"
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ from mcp.types import ( # noqa: E402
|
||||
ServerCapabilities,
|
||||
TextContent,
|
||||
Tool,
|
||||
ToolAnnotations,
|
||||
ToolsCapability,
|
||||
)
|
||||
|
||||
@@ -559,11 +560,16 @@ async def handle_list_tools() -> list[Tool]:
|
||||
|
||||
# Add all registered AI-powered tools from the TOOLS registry
|
||||
for tool in TOOLS.values():
|
||||
# Get optional annotations from the tool
|
||||
annotations = tool.get_annotations()
|
||||
tool_annotations = ToolAnnotations(**annotations) if annotations else None
|
||||
|
||||
tools.append(
|
||||
Tool(
|
||||
name=tool.name,
|
||||
description=tool.description,
|
||||
inputSchema=tool.get_input_schema(),
|
||||
annotations=tool_annotations,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -43,11 +43,11 @@ class ListModelsTool(BaseTool):
|
||||
|
||||
def get_input_schema(self) -> dict[str, Any]:
|
||||
"""Return the JSON schema for the tool's input"""
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {"model": {"type": "string", "description": "Model to use (ignored by listmodels tool)"}},
|
||||
"required": [],
|
||||
}
|
||||
return {"type": "object", "properties": {}, "required": []}
|
||||
|
||||
def get_annotations(self) -> Optional[dict[str, Any]]:
|
||||
"""Return tool annotations indicating this is a read-only tool"""
|
||||
return {"readOnlyHint": True}
|
||||
|
||||
def get_system_prompt(self) -> str:
|
||||
"""No AI model needed for this tool"""
|
||||
@@ -57,6 +57,9 @@ class ListModelsTool(BaseTool):
|
||||
"""Return the Pydantic model for request validation."""
|
||||
return ToolRequest
|
||||
|
||||
def requires_model(self) -> bool:
|
||||
return False
|
||||
|
||||
async def prepare_prompt(self, request: ToolRequest) -> str:
|
||||
"""Not used for this utility tool"""
|
||||
return ""
|
||||
|
||||
@@ -153,6 +153,19 @@ class BaseTool(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_annotations(self) -> Optional[dict[str, Any]]:
|
||||
"""
|
||||
Return optional annotations for this tool.
|
||||
|
||||
Annotations provide hints about tool behavior without being security-critical.
|
||||
They help MCP clients make better decisions about tool usage.
|
||||
|
||||
Returns:
|
||||
Optional[dict]: Dictionary with annotation fields like readOnlyHint, destructiveHint, etc.
|
||||
Returns None if no annotations are needed.
|
||||
"""
|
||||
return None
|
||||
|
||||
def requires_model(self) -> bool:
|
||||
"""
|
||||
Return whether this tool requires AI model access.
|
||||
|
||||
@@ -100,6 +100,21 @@ class SimpleTool(BaseTool):
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_annotations(self) -> Optional[dict[str, Any]]:
|
||||
"""
|
||||
Return tool annotations. Simple tools are read-only by default.
|
||||
|
||||
All simple tools perform operations without modifying the environment.
|
||||
They may call external AI models for analysis or conversation, but they
|
||||
don't write files or make system changes.
|
||||
|
||||
Override this method if your simple tool needs different annotations.
|
||||
|
||||
Returns:
|
||||
Dictionary with readOnlyHint set to True
|
||||
"""
|
||||
return {"readOnlyHint": True}
|
||||
|
||||
def format_response(self, response: str, request, model_info: Optional[dict] = None) -> str:
|
||||
"""
|
||||
Format the AI response before returning to the client.
|
||||
|
||||
@@ -147,11 +147,11 @@ class VersionTool(BaseTool):
|
||||
|
||||
def get_input_schema(self) -> dict[str, Any]:
|
||||
"""Return the JSON schema for the tool's input"""
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {"model": {"type": "string", "description": "Model to use (ignored by version tool)"}},
|
||||
"required": [],
|
||||
}
|
||||
return {"type": "object", "properties": {}, "required": []}
|
||||
|
||||
def get_annotations(self) -> Optional[dict[str, Any]]:
|
||||
"""Return tool annotations indicating this is a read-only tool"""
|
||||
return {"readOnlyHint": True}
|
||||
|
||||
def get_system_prompt(self) -> str:
|
||||
"""No AI model needed for this tool"""
|
||||
@@ -161,6 +161,9 @@ class VersionTool(BaseTool):
|
||||
"""Return the Pydantic model for request validation."""
|
||||
return ToolRequest
|
||||
|
||||
def requires_model(self) -> bool:
|
||||
return False
|
||||
|
||||
async def prepare_prompt(self, request: ToolRequest) -> str:
|
||||
"""Not used for this utility tool"""
|
||||
return ""
|
||||
|
||||
@@ -110,6 +110,21 @@ class WorkflowTool(BaseTool, BaseWorkflowMixin):
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_annotations(self) -> Optional[dict[str, Any]]:
|
||||
"""
|
||||
Return tool annotations. Workflow tools are read-only by default.
|
||||
|
||||
All workflow tools perform analysis and investigation without modifying
|
||||
the environment. They may call external AI models for expert analysis,
|
||||
but they don't write files or make system changes.
|
||||
|
||||
Override this method if your workflow tool needs different annotations.
|
||||
|
||||
Returns:
|
||||
Dictionary with readOnlyHint set to True
|
||||
"""
|
||||
return {"readOnlyHint": True}
|
||||
|
||||
def get_input_schema(self) -> dict[str, Any]:
|
||||
"""
|
||||
Generate the complete input schema using SchemaBuilder.
|
||||
|
||||
Reference in New Issue
Block a user