1package main
2
3import (
4 "crypto/sha256"
5 "encoding/hex"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io/fs"
10 "os"
11 "path/filepath"
12
13 "github.com/mark3labs/mcp-go/mcp"
14)
15
16const toolCacheVersion = 1
17
18type cachedTool struct {
19 Name string `json:"name"`
20 Description string `json:"description"`
21 Schema json.RawMessage `json:"schema"`
22}
23
24type cachedServer struct {
25 Fingerprint string `json:"fingerprint"`
26 Tools []cachedTool `json:"tools"`
27}
28
29type toolCache struct {
30 Version int `json:"version"`
31 Servers map[string]cachedServer `json:"servers"`
32}
33
34func toolCachePath() string {
35 return filepath.Join(zutHome(), "mcp-tools-cache.json")
36}
37
38func serverFingerprint(cfg ServerConfig) string {
39 data, _ := json.Marshal(cfg)
40 sum := sha256.Sum256(data)
41 return hex.EncodeToString(sum[:])
42}
43
44func readToolCache(path string) (toolCache, error) {
45 cache := toolCache{Version: toolCacheVersion, Servers: map[string]cachedServer{}}
46 data, err := os.ReadFile(path)
47 if err != nil {
48 if errors.Is(err, fs.ErrNotExist) {
49 return cache, nil
50 }
51 return cache, err
52 }
53 if err := json.Unmarshal(data, &cache); err != nil {
54 return cache, fmt.Errorf("parse %s: %w", path, err)
55 }
56 if cache.Version != toolCacheVersion || cache.Servers == nil {
57 return toolCache{Version: toolCacheVersion, Servers: map[string]cachedServer{}}, nil
58 }
59 return cache, nil
60}
61
62func toolCachesEqual(a, b toolCache) bool {
63 aj, err := json.Marshal(a)
64 if err != nil {
65 return false
66 }
67 bj, err := json.Marshal(b)
68 if err != nil {
69 return false
70 }
71 return string(aj) == string(bj)
72}
73
74func writeToolCache(path string, cache toolCache) error {
75 if cache.Servers == nil {
76 cache.Servers = map[string]cachedServer{}
77 }
78 cache.Version = toolCacheVersion
79 data, err := json.MarshalIndent(cache, "", " ")
80 if err != nil {
81 return err
82 }
83 data = append(data, '\n')
84 return writeFileAtomic(path, data, 0o600)
85}
86
87func cachedToolFromMCP(serverName string, tool mcp.Tool) (cachedTool, error) {
88 schema := mcpToolSchema(tool)
89 schemaJSON, err := json.Marshal(schema)
90 if err != nil {
91 return cachedTool{}, err
92 }
93 return cachedTool{
94 Name: tool.Name,
95 Description: mcpToolDescription(serverName, tool),
96 Schema: json.RawMessage(schemaJSON),
97 }, nil
98}