|
8 | 8 | "encoding/json" |
9 | 9 | "fmt" |
10 | 10 | "reflect" |
| 11 | + "strings" |
11 | 12 |
|
12 | 13 | "github.com/google/jsonschema-go/jsonschema" |
13 | 14 | ) |
@@ -65,7 +66,8 @@ func createTypedHandler[T any, U any](handler func(T, ToolInvocation) (U, error) |
65 | 66 | } |
66 | 67 |
|
67 | 68 | // normalizeResult converts any value to a ToolResult. |
68 | | -// Strings pass through directly, ToolResult passes through, other types are JSON-serialized. |
| 69 | +// Strings pass through directly, ToolResult passes through, and other types |
| 70 | +// are JSON-serialized. |
69 | 71 | func normalizeResult(result any) (ToolResult, error) { |
70 | 72 | if result == nil { |
71 | 73 | return ToolResult{ |
@@ -99,6 +101,104 @@ func normalizeResult(result any) (ToolResult, error) { |
99 | 101 | }, nil |
100 | 102 | } |
101 | 103 |
|
| 104 | +// ConvertMCPCallToolResult converts an MCP CallToolResult value (a map or struct |
| 105 | +// with a "content" array and optional "isError" bool) into a ToolResult. |
| 106 | +// Returns the converted ToolResult and true if the value matched the expected |
| 107 | +// shape, or a zero ToolResult and false otherwise. |
| 108 | +func ConvertMCPCallToolResult(value any) (ToolResult, bool) { |
| 109 | + m, ok := value.(map[string]any) |
| 110 | + if !ok { |
| 111 | + jsonBytes, err := json.Marshal(value) |
| 112 | + if err != nil { |
| 113 | + return ToolResult{}, false |
| 114 | + } |
| 115 | + |
| 116 | + if err := json.Unmarshal(jsonBytes, &m); err != nil { |
| 117 | + return ToolResult{}, false |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + contentRaw, exists := m["content"] |
| 122 | + if !exists { |
| 123 | + return ToolResult{}, false |
| 124 | + } |
| 125 | + |
| 126 | + contentSlice, ok := contentRaw.([]any) |
| 127 | + if !ok { |
| 128 | + return ToolResult{}, false |
| 129 | + } |
| 130 | + |
| 131 | + // Verify every element has a string "type" field |
| 132 | + for _, item := range contentSlice { |
| 133 | + block, ok := item.(map[string]any) |
| 134 | + if !ok { |
| 135 | + return ToolResult{}, false |
| 136 | + } |
| 137 | + if _, ok := block["type"].(string); !ok { |
| 138 | + return ToolResult{}, false |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + var textParts []string |
| 143 | + var binaryResults []ToolBinaryResult |
| 144 | + |
| 145 | + for _, item := range contentSlice { |
| 146 | + block := item.(map[string]any) |
| 147 | + blockType := block["type"].(string) |
| 148 | + |
| 149 | + switch blockType { |
| 150 | + case "text": |
| 151 | + if text, ok := block["text"].(string); ok { |
| 152 | + textParts = append(textParts, text) |
| 153 | + } |
| 154 | + case "image": |
| 155 | + data, _ := block["data"].(string) |
| 156 | + mimeType, _ := block["mimeType"].(string) |
| 157 | + if data == "" { |
| 158 | + continue |
| 159 | + } |
| 160 | + binaryResults = append(binaryResults, ToolBinaryResult{ |
| 161 | + Data: data, |
| 162 | + MimeType: mimeType, |
| 163 | + Type: "image", |
| 164 | + }) |
| 165 | + case "resource": |
| 166 | + if resRaw, ok := block["resource"].(map[string]any); ok { |
| 167 | + if text, ok := resRaw["text"].(string); ok && text != "" { |
| 168 | + textParts = append(textParts, text) |
| 169 | + } |
| 170 | + if blob, ok := resRaw["blob"].(string); ok && blob != "" { |
| 171 | + mimeType, _ := resRaw["mimeType"].(string) |
| 172 | + if mimeType == "" { |
| 173 | + mimeType = "application/octet-stream" |
| 174 | + } |
| 175 | + uri, _ := resRaw["uri"].(string) |
| 176 | + binaryResults = append(binaryResults, ToolBinaryResult{ |
| 177 | + Data: blob, |
| 178 | + MimeType: mimeType, |
| 179 | + Type: "resource", |
| 180 | + Description: uri, |
| 181 | + }) |
| 182 | + } |
| 183 | + } |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | + resultType := "success" |
| 188 | + if isErr, ok := m["isError"].(bool); ok && isErr { |
| 189 | + resultType = "failure" |
| 190 | + } |
| 191 | + |
| 192 | + tr := ToolResult{ |
| 193 | + TextResultForLLM: strings.Join(textParts, "\n"), |
| 194 | + ResultType: resultType, |
| 195 | + } |
| 196 | + if len(binaryResults) > 0 { |
| 197 | + tr.BinaryResultsForLLM = binaryResults |
| 198 | + } |
| 199 | + return tr, true |
| 200 | +} |
| 201 | + |
102 | 202 | // generateSchemaForType generates a JSON schema map from a Go type using reflection. |
103 | 203 | // Panics if schema generation fails, as this indicates a programming error. |
104 | 204 | func generateSchemaForType(t reflect.Type) map[string]any { |
|
0 commit comments