samplingContentToChat maps a single MCP content block to the host's chat representation. Text blocks return a Content string; image blocks return a MultiContent entry with a data URL the model can consume. Audio blocks fall back to a textual placeholder because chat.Message does not currently model
(c mcp.Content)
| 165 | // are rejected so a malicious or buggy server can't pin large blobs in |
| 166 | // host memory. |
| 167 | func samplingContentToChat(c mcp.Content) (string, []chat.MessagePart, error) { |
| 168 | switch v := c.(type) { |
| 169 | case *mcp.TextContent: |
| 170 | if len(v.Text) > maxSamplingTextBytes { |
| 171 | return "", nil, fmt.Errorf("text block too large (%d bytes, limit %d)", |
| 172 | len(v.Text), maxSamplingTextBytes) |
| 173 | } |
| 174 | return v.Text, nil, nil |
| 175 | case *mcp.ImageContent: |
| 176 | if len(v.Data) > maxSamplingBinaryBytes { |
| 177 | return "", nil, fmt.Errorf("image block too large (%d bytes, limit %d)", |
| 178 | len(v.Data), maxSamplingBinaryBytes) |
| 179 | } |
| 180 | return "", []chat.MessagePart{{ |
| 181 | Type: chat.MessagePartTypeImageURL, |
| 182 | ImageURL: &chat.MessageImageURL{ |
| 183 | URL: dataURL(v.MIMEType, v.Data), |
| 184 | }, |
| 185 | }}, nil |
| 186 | case *mcp.AudioContent: |
| 187 | if len(v.Data) > maxSamplingBinaryBytes { |
| 188 | return "", nil, fmt.Errorf("audio block too large (%d bytes, limit %d)", |
| 189 | len(v.Data), maxSamplingBinaryBytes) |
| 190 | } |
| 191 | return fmt.Sprintf("[audio attachment (%s, %d bytes) — not inlined]", |
| 192 | v.MIMEType, len(v.Data)), nil, nil |
| 193 | case nil: |
| 194 | return "", nil, nil |
| 195 | default: |
| 196 | return fmt.Sprintf("[unsupported content type %T]", v), nil, nil |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | func dataURL(mimeType string, data []byte) string { |
| 201 | mt := mimeType |
no test coverage detected