From 2f0e1143f77e3e42f009d4943f1ab9d6ecaaeb58 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Wed, 11 Nov 2026 01:55:46 +0100 Subject: [PATCH 1/2] open_chat: describe the phone format without a real-looking number The parameter description and the refusal both gave a full Danish mobile number as the example. A model that has no number and follows the example opens a WhatsApp chat with it: open_chat builds whatsapp://send?phone= and computer_open is not on the always-ask list, so in the default background mode nothing asks first. Any digits used as an example could belong to someone, so the fix is to describe the format instead of showing one. 0.1.0 had no open_chat (it arrived in c063058, 29/9), so without this 0.4.1 would have introduced the path rather than kept it. Co-Authored-By: Claude Opus 5.5 --- mcp-server/index.js | 2 +- mcp-server/tools.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mcp-server/index.js b/mcp-server/index.js index 39ad09b..b6dd28c 100644 --- a/mcp-server/index.js +++ b/mcp-server/index.js @@ -1158,7 +1158,7 @@ async function runTool(name, args) { url = `spotify:track:${id}`; } else if (intent === 'open_chat') { const phone = String(args.phone || '').replace(/[\s()-]/g, ''); - if (!/^\+?\d{4,15}$/.test(phone)) return errorResult('Refused: open_chat needs a phone number in international form, e.g. +4560174569. Nothing was done.'); + if (!/^\+?\d{4,15}$/.test(phone)) return errorResult('Refused: open_chat needs a phone number in international form - a plus sign, the country code and the number, digits only. Nothing was done.'); url = `whatsapp://send?phone=${encodeURIComponent(phone.replace(/^\+/, ''))}`; } else { return errorResult(`Refused: unknown intent '${intent}'. Use open_app, play_track or open_chat. Nothing was done.`); diff --git a/mcp-server/tools.js b/mcp-server/tools.js index e726959..deae660 100644 --- a/mcp-server/tools.js +++ b/mcp-server/tools.js @@ -58,7 +58,7 @@ export const TOOLS = [ description: 'open_app: bring an app up in the background (needs bundleId). play_track: start a Spotify track (needs spotifyId, 22 chars). open_chat: open a WhatsApp chat to a phone number with NO pre-filled text (needs phone).' }, bundleId: { type: 'string', description: 'For open_app: the app, e.g. com.spotify.client.' }, spotifyId: { type: 'string', description: 'For play_track: the 22-character Spotify track id.' }, - phone: { type: 'string', description: 'For open_chat: the phone number in international form, e.g. +4560174569.' } + phone: { type: 'string', description: 'For open_chat: the phone number in international form - a plus sign, the country code and the number, digits only. Use the number the user gave you; never make one up.' } } } }, From 9eeabffbb1eecf65a8b2fdd106badf5cde8f4c57 Mon Sep 17 00:00:00 2001 From: Gustav Louv Date: Wed, 11 Nov 2026 01:57:23 +0100 Subject: [PATCH 2/2] README: password managers are left out of redacted screenshots, not every one The README said a password manager "can never be photographed on its own". The helper leaves deny-listed apps out of the capture only when redacting (Capture.swift: the exclusion sits inside `if redact {`). Redaction is the default, and an unredacted screenshot is the person's decision, never the model's - but in that case the window is captured like any other, so "never" was not true. An npm README is frozen per version, so the wording is fixed before 0.4.1 rather than after. The npm README is derived from this file by sync-tal.py. Co-Authored-By: Claude Opus 5.5 --- README.md | 10 ++++++---- mcp-server/README.md | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8560152..4402ba7 100644 --- a/README.md +++ b/README.md @@ -511,10 +511,12 @@ sit unnoticed until it expires. That is a real limitation today, not a setting. `readonly` does not help here: it still takes screenshots. Close such a window first, or keep screenshots to one app with `app` - covering it with another window is not enough, because a capture can leave the covering window out. -- **Password managers are kept out of the picture itself.** The capture asks - macOS to leave every app on the deny list out of the image, so their pixels - never exist - hiding, moving or closing one mid-capture cannot leak it, and - one can never be photographed on its own. Redaction still runs on top. +- **Password managers are kept out of the picture itself.** In a redacted + screenshot - the default - the capture asks macOS to leave every app on the + deny list out of the image, so their pixels never exist: hiding, moving or + closing one mid-capture cannot leak it. Redaction still runs on top. An + unredacted screenshot, which only the person can allow, captures them like + any other window. What is left of the gap: a secure *field* in an ordinary app that moves while the scan runs is painted where it is now, not where it was. A field shows dots, so that reveals its length, not the password. Helper processes a diff --git a/mcp-server/README.md b/mcp-server/README.md index 2677537..fc94f53 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -509,10 +509,12 @@ sit unnoticed until it expires. That is a real limitation today, not a setting. `readonly` does not help here: it still takes screenshots. Close such a window first, or keep screenshots to one app with `app` - covering it with another window is not enough, because a capture can leave the covering window out. -- **Password managers are kept out of the picture itself.** The capture asks - macOS to leave every app on the deny list out of the image, so their pixels - never exist - hiding, moving or closing one mid-capture cannot leak it, and - one can never be photographed on its own. Redaction still runs on top. +- **Password managers are kept out of the picture itself.** In a redacted + screenshot - the default - the capture asks macOS to leave every app on the + deny list out of the image, so their pixels never exist: hiding, moving or + closing one mid-capture cannot leak it. Redaction still runs on top. An + unredacted screenshot, which only the person can allow, captures them like + any other window. What is left of the gap: a secure *field* in an ordinary app that moves while the scan runs is painted where it is now, not where it was. A field shows dots, so that reveals its length, not the password. Helper processes a