Problem
Composite tools don't expose a way to set MCP tool annotations — readOnlyHint, destructiveHint, idempotentHint, openWorldHint. Clients that use these hints to gate confirmation prompts or filter destructive actions have no signal for composite tools, even when the workflow author knows the semantics (e.g. a composite tool that only reads data, or one that deletes resources).
The vmcp.Tool.Annotations type already models all four hints (pkg/vmcp/types.go), and regular backend/aggregated tools can already override annotations via ToolAnnotationsOverride in the aggregation config (pkg/vmcp/aggregator/tool_adapter.go, manual_resolver.go, priority_resolver.go). Composite tools never wire into this path.
Current behavior
CompositeToolConfig (pkg/vmcp/config/config.go:682-720) has no annotations field.
ConvertWorkflowDefsToTools (pkg/vmcp/internal/compositetools/workflow_converter.go:121-143) builds each vmcp.Tool with only Name, Description, InputSchema, and optionally OutputSchema — Annotations is left nil.
- The operator CRD
VirtualMCPCompositeToolDefinitionSpec (cmd/thv-operator/api/v1beta1/virtualmcpcompositetooldefinition_types.go) embeds CompositeToolConfig inline, so it inherits the same gap.
Proposed change
- Add an
Annotations *vmcp.ToolAnnotations (or equivalent) field to CompositeToolConfig, settable in the workflow YAML/CRD, for explicit author-provided annotations.
- Populate
tool.Annotations in ConvertWorkflowDefsToTools from this field.
- When no explicit annotation is provided, derive a safe default per hint from the workflow's underlying step tools:
readOnlyHint: AND across steps — true only if every step tool sets readOnlyHint: true. A step with the hint unset or false makes the composite non-read-only.
destructiveHint: OR across steps — true if any step is destructive or its destructiveness is unknown (unset). One destructive write anywhere in the chain taints the whole workflow.
openWorldHint: OR across steps, same reasoning — one call to an unpredictable external system makes the composite open-world too.
idempotentHint: don't auto-derive. All-steps-idempotent doesn't imply the workflow is idempotent as a whole (e.g. step 2 branches on step 1's output and behaves differently on retry). Leave unset unless the author explicitly opts in.
- Add a validation guardrail for explicit overrides: reject (at config load/admission time) any explicit annotation that contradicts the derived floor — e.g.
readOnlyHint: true while any step carries destructiveHint: true or lacks readOnlyHint.
- Regenerate CRD manifests and deepcopy code (
task operator-generate).
- Update composite tool docs/examples to show how to set annotations.
Problem
Composite tools don't expose a way to set MCP tool annotations —
readOnlyHint,destructiveHint,idempotentHint,openWorldHint. Clients that use these hints to gate confirmation prompts or filter destructive actions have no signal for composite tools, even when the workflow author knows the semantics (e.g. a composite tool that only reads data, or one that deletes resources).The
vmcp.Tool.Annotationstype already models all four hints (pkg/vmcp/types.go), and regular backend/aggregated tools can already override annotations viaToolAnnotationsOverridein the aggregation config (pkg/vmcp/aggregator/tool_adapter.go,manual_resolver.go,priority_resolver.go). Composite tools never wire into this path.Current behavior
CompositeToolConfig(pkg/vmcp/config/config.go:682-720) has no annotations field.ConvertWorkflowDefsToTools(pkg/vmcp/internal/compositetools/workflow_converter.go:121-143) builds eachvmcp.Toolwith onlyName,Description,InputSchema, and optionallyOutputSchema—Annotationsis left nil.VirtualMCPCompositeToolDefinitionSpec(cmd/thv-operator/api/v1beta1/virtualmcpcompositetooldefinition_types.go) embedsCompositeToolConfiginline, so it inherits the same gap.Proposed change
Annotations *vmcp.ToolAnnotations(or equivalent) field toCompositeToolConfig, settable in the workflow YAML/CRD, for explicit author-provided annotations.tool.AnnotationsinConvertWorkflowDefsToToolsfrom this field.readOnlyHint: AND across steps — true only if every step tool setsreadOnlyHint: true. A step with the hint unset or false makes the composite non-read-only.destructiveHint: OR across steps — true if any step is destructive or its destructiveness is unknown (unset). One destructive write anywhere in the chain taints the whole workflow.openWorldHint: OR across steps, same reasoning — one call to an unpredictable external system makes the composite open-world too.idempotentHint: don't auto-derive. All-steps-idempotent doesn't imply the workflow is idempotent as a whole (e.g. step 2 branches on step 1's output and behaves differently on retry). Leave unset unless the author explicitly opts in.readOnlyHint: truewhile any step carriesdestructiveHint: trueor lacksreadOnlyHint.task operator-generate).