Skip to content

Commit 592b4ac

Browse files
mudlerclaude
andcommitted
fix(mcp): deliver the SOM element list to the model as text
The driver returns clickable elements only as structured data, and the model's tool message collapsed to a bare "[image content …]" placeholder — so the model saw a screenshot with no numbers and no element list, and could never issue a click (it just re-captured). Prepend a numbered, model-readable Set-of-Marks list ("N: role \"label\"") to the tool result for som/ax captures so the model can act with element=N. Log the element count for diagnosis. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A8Z5dz1nVHAQUpjb5pgGCZ
1 parent 205f52c commit 592b4ac

1 file changed

Lines changed: 28 additions & 0 deletions

File tree

mcp/computer.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,24 +194,52 @@ func (c *computerServer) capture(ctx context.Context, in ComputerUseInput) (*mcp
194194
case "ax":
195195
st.Content = filterOutImages(st.Content)
196196
els := parseElements(structuredMap(st), in.MaxElements)
197+
st.Content = withElementText(st.Content, els)
197198
summary := "captured window (ax)"
198199
if len(els) == 0 {
199200
summary = "captured window (ax)" + noAXHint
200201
}
202+
xlog.Debug("computer capture", "mode", "ax", "elements", len(els))
201203
return st, ComputerUseOutput{Summary: summary, Elements: els}, nil
202204
case "vision":
203205
// Pixels only — drop the AX-tree noise, keep just the screenshot.
204206
return st, ComputerUseOutput{Summary: "captured screen", ImageMIME: imageMIME()}, nil
205207
default: // som
206208
els := parseElements(structuredMap(st), in.MaxElements)
209+
// Surface the clickable elements as TEXT alongside the screenshot. The
210+
// driver returns them only as structured data, and the model's tool
211+
// message otherwise collapses to a bare "[image content …]" placeholder —
212+
// so without this the model sees a picture with no numbers to reference
213+
// and can never issue a click. This is the model-facing Set-of-Marks list.
214+
st.Content = withElementText(st.Content, els)
207215
summary := "captured window"
208216
if len(els) == 0 {
209217
summary = "captured screen" + noAXHint
210218
}
219+
xlog.Debug("computer capture", "mode", "som", "elements", len(els))
211220
return st, ComputerUseOutput{Summary: summary, ImageMIME: imageMIME(), Elements: els}, nil
212221
}
213222
}
214223

224+
// withElementText prepends a numbered, model-readable list of the clickable
225+
// elements to the tool result, so a model can pick an element index to act on
226+
// (click/type/scroll with `element: N`). No-op when there are no elements.
227+
func withElementText(cs []mcp.Content, els []ComputerElement) []mcp.Content {
228+
if len(els) == 0 {
229+
return cs
230+
}
231+
var b strings.Builder
232+
b.WriteString("Clickable elements (act with computer_use action=\"click\"/\"type\"/\"scroll\" and element=N):\n")
233+
for _, e := range els {
234+
label := e.Label
235+
if label == "" {
236+
label = "(no label)"
237+
}
238+
fmt.Fprintf(&b, " %d: %s %q\n", e.Index, e.Role, label)
239+
}
240+
return append([]mcp.Content{&mcp.TextContent{Text: b.String()}}, cs...)
241+
}
242+
215243
func firstText(cs []mcp.Content) string {
216244
for _, c := range cs {
217245
if t, ok := c.(*mcp.TextContent); ok && t.Text != "" {

0 commit comments

Comments
 (0)