mcp
This commit is contained in:
1 parent
9f578780bd
commit
5807132ac6
6 files changed
+838
-81
No files matched your search
+64
-3
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Tool is one MCP tool: its advertised schema plus the Invoke func that parses
|
||||
@@ -12,9 +13,12 @@ import (
|
||||
// error. The context carries request scope (deadline, cancellation, trace span);
|
||||
// it flows from the HTTP request through to Invoke.
|
||||
type Tool struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InputSchema json.RawMessage `json:"inputSchema"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InputSchema json.RawMessage `json:"inputSchema"`
|
||||
// Annotations are optional hints about the tool's behaviour; nil means
|
||||
// unannotated (clients should then assume the safest interpretation).
|
||||
Annotations *ToolAnnotations `json:"annotations,omitempty"`
|
||||
Invoke func(ctx context.Context, args json.RawMessage) (any, error) `json:"-"`
|
||||
}
|
||||
|
||||
@@ -38,6 +42,63 @@ func ErrorResult(err error) any {
|
||||
}
|
||||
}
|
||||
|
||||
// ToolAnnotations is the MCP tool-annotations subset that matters here:
|
||||
// hints clients (and our own agent loop) use to treat calls differently —
|
||||
// e.g. auto-run reads, confirm destructive writes. Both fields are always
|
||||
// serialized so absence never falls back to the spec's dangerous defaults.
|
||||
type ToolAnnotations struct {
|
||||
ReadOnlyHint bool `json:"readOnlyHint"`
|
||||
DestructiveHint bool `json:"destructiveHint"`
|
||||
}
|
||||
|
||||
// Shared annotation presets.
|
||||
var (
|
||||
// ReadOnly: pure queries — safe to run without confirmation.
|
||||
ReadOnly = &ToolAnnotations{ReadOnlyHint: true}
|
||||
// Mutable: writes state but is not destructive (create/update/register).
|
||||
Mutable = &ToolAnnotations{}
|
||||
// Destructive: deletes, reversals, bulk overwrites.
|
||||
Destructive = &ToolAnnotations{DestructiveHint: true}
|
||||
)
|
||||
|
||||
// AnnotateByPrefix fills in Annotations for tools that don't carry one yet,
|
||||
// using the repo-wide tool naming convention:
|
||||
//
|
||||
// get_*/list_*/search_*/find_*/suggest_*/describe_* → ReadOnly
|
||||
// delete_*/remove_*/reverse_*/import_*/purge_* → Destructive
|
||||
// export_*/calculate_*/verify_*/preview_* → ReadOnly (pure computation)
|
||||
//
|
||||
// Tools that set Annotations explicitly are left untouched, so a package can
|
||||
// override individual cases after calling this.
|
||||
func AnnotateByPrefix(tools []Tool) {
|
||||
for i := range tools {
|
||||
if tools[i].Annotations != nil {
|
||||
continue
|
||||
}
|
||||
name := strings.ToLower(tools[i].Name)
|
||||
switch {
|
||||
case hasAny(name,
|
||||
"get_", "list_", "search_", "find_", "suggest_",
|
||||
"export_", "calculate_", "verify_", "preview_"):
|
||||
tools[i].Annotations = ReadOnly
|
||||
case hasAny(name,
|
||||
"delete_", "remove_", "reverse_", "import_", "purge_"):
|
||||
tools[i].Annotations = Destructive
|
||||
default:
|
||||
tools[i].Annotations = Mutable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasAny(name string, prefixes ...string) bool {
|
||||
for _, p := range prefixes {
|
||||
if strings.HasPrefix(name, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Decode unmarshals tool arguments into v, treating empty/null args as a no-op
|
||||
// (zero value). Returns a wrapped error suitable for returning from Invoke.
|
||||
func Decode(args json.RawMessage, v any) error {
|
||||
|
||||
Reference in new issue
Block a user