@yunori/mcp 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * personal API token, so the server's scope check decides what happens; this
4
4
  * only makes sure the token goes to one place and that failures read clearly.
5
5
  */
6
- export const VERSION = "0.1.0";
6
+ export const VERSION = "0.1.1";
7
7
  export const DEFAULT_BASE_URL = "https://yunori.email";
8
8
  const TOKEN_SHAPE = /^tori_pat_[A-Za-z0-9]{32}$/;
9
9
  const TIMEOUT_MS = 30_000;
package/dist/mail-text.js CHANGED
@@ -229,20 +229,30 @@ export function draftBody(parts) {
229
229
  }
230
230
  // ── Handing mail to a model ─────────────────────────────────────────────────
231
231
  /**
232
- * Characters a person never sees and a model reads anyway: the Tags block
233
- * (U+E0000–E007F, invisible ASCII), zero-width characters and direction
234
- * overrides. A sender can hide instructions in these.
232
+ * Characters a person never sees and a model reads anyway, which a sender can
233
+ * hide instructions in or split a tag with: the Tags block (invisible ASCII),
234
+ * zero-width characters, direction overrides, soft hyphens, variation selectors
235
+ * and the rest of what Unicode calls default-ignorable, and every control
236
+ * character but tab and newline. `sdk/mcp` carries the same patterns, and the
237
+ * test holds them equal.
235
238
  */
236
- const INVISIBLE = /[\u{E0000}-\u{E007F}\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]/gu;
237
- /** A sender can write the closing tag too, so nothing that reads as one survives inside. */
238
- const FENCE_LOOKALIKE = /<\s*\/?\s*untrusted_mail/gi;
239
+ const INVISIBLE = /(?![\t\n\r])[\p{Default_Ignorable_Code_Point}\p{Cc}]/gu;
240
+ /**
241
+ * A sender can write the closing tag too, so nothing that reads as one survives
242
+ * inside: any case, spaced out, backslashed, in wide characters. Matching only as
243
+ * far as "untrusted" takes in `<untrusted_email>` from `@yunori/agent-mail-mcp`
244
+ * too, which may be feeding the same model.
245
+ */
246
+ const FENCE_LOOKALIKE = /[<<﹤][\s\\//]*(untrusted)/giu;
247
+ const defuse = (text) => text.replace(INVISIBLE, "").replace(FENCE_LOOKALIKE, "[$1");
239
248
  /**
240
249
  * Anything strangers had a hand in, wrapped so a model can tell it from what it
241
250
  * was asked. This is a signal to the model, not a wall: the wall is the token's
242
251
  * scopes, which is why the docs tell people to leave Send unticked.
243
252
  */
244
253
  export function fenceMail(value) {
245
- const json = JSON.stringify(value, null, 2) ?? "null";
246
- const safe = json.replace(INVISIBLE, "").replace(FENCE_LOOKALIKE, "[untrusted_mail");
247
- return `<untrusted_mail>\n${safe}\n</untrusted_mail>`;
254
+ // String by string, because once this is JSON a newline in a tag is the two
255
+ // characters \n and the pattern can't see through it. Then the whole, for keys.
256
+ const json = JSON.stringify(value, (_key, v) => (typeof v === "string" ? defuse(v) : v), 2) ?? "null";
257
+ return `<untrusted_mail>\n${defuse(json)}\n</untrusted_mail>`;
248
258
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yunori/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "MCP server for your Yunori mail: let an AI assistant read your mailboxes, organise them and write drafts, with a token you control.",
5
5
  "license": "MIT",
6
6
  "author": "FutureForge Studios",