apple-mail-mcp 2.10.11 → 2.10.13

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.
Files changed (3) hide show
  1. package/README.md +23 -6
  2. package/build/index.js +112 -1
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -1375,21 +1375,33 @@ When sending content containing backslashes (`\`) to this MCP server, **you must
1375
1375
 
1376
1376
  **Why:** The MCP protocol uses JSON for parameter passing. In JSON, a single backslash is an escape character. To include a literal backslash in content, it must be escaped as `\\`.
1377
1377
 
1378
- **Example - Email with file path:**
1378
+ **Correct email containing a shell path with an escaped space:**
1379
+
1379
1380
  ```json
1380
1381
  {
1381
1382
  "to": ["colleague@company.com"],
1382
1383
  "subject": "File Location",
1383
- "body": "The file is at C:\\\\Users\\\\Documents\\\\report.pdf"
1384
+ "body": "Run: cp ~/Library/Mobile\\ Documents/report.pdf ~/Desktop/"
1384
1385
  }
1385
1386
  ```
1386
1387
 
1387
- The `\\\\` in JSON becomes `\\` in the actual string, which represents a single `\` in the email.
1388
+ arrives as: `Run: cp ~/Library/Mobile\ Documents/report.pdf ~/Desktop/`
1389
+
1390
+ In a JSON string literal, `\\` — two characters — denotes **one** literal backslash. Four backslashes (`\\\\`) denote **two** literal backslashes, so send those only when the text genuinely contains `\\`.
1391
+
1392
+ **Incorrect — the unescaped backslash makes this invalid JSON:**
1393
+
1394
+ ```text
1395
+ "body": "Run: cp ~/Library/Mobile\ Documents/report.pdf ~/Desktop/"
1396
+ ```
1397
+
1398
+ `\ ` (backslash-space) is not a valid JSON escape sequence, so the call is rejected — or, with a laxer parser, the backslash is silently dropped.
1388
1399
 
1389
1400
  **Common patterns requiring escaping:**
1390
- - Windows paths: `C:\Users\` → `C:\\\\Users\\\\` in JSON
1391
- - Shell escaped spaces: `Mobile\ Documents` → `Mobile\\\\ Documents` in JSON
1392
- - Regex patterns: `\d+` → `\\\\d+` in JSON
1401
+
1402
+ - Shell escaped spaces: `Mobile\ Documents` → `Mobile\\ Documents` in JSON
1403
+ - Regex patterns: `\d+` → `\\d+` in JSON
1404
+ - A literal double backslash: `\\` → `\\\\` in JSON
1393
1405
 
1394
1406
  **If you see errors** when sending emails with backslashes, double-check that backslashes are properly escaped in the JSON payload.
1395
1407
 
@@ -1427,6 +1439,11 @@ The `\\\\` in JSON becomes `\\` in the actual string, which represents a single
1427
1439
  - Verify Mail.app can send emails manually
1428
1440
  - Check if the account is configured correctly in Mail.app
1429
1441
 
1442
+ ### "invalid outputSchema … unsupported dialect" — every tool is refused
1443
+ - Full text: `Tool '<name>' has an invalid outputSchema: JSON Schema declares an unsupported dialect ("$schema": "http://json-schema.org/draft-07/schema#"). The default validator supports JSON Schema 2020-12 only.` The server connects, but **no tool is usable**.
1444
+ - **Upgrade to 2.10.12 or later.** Earlier versions advertised their tool schemas in JSON Schema **draft-07** (the MCP SDK's converter default); MCP has since standardized on **2020-12** and clients reject anything else. 2.10.12 normalizes every advertised `inputSchema`/`outputSchema` to 2020-12 on the way out. See [issue #147](https://github.com/sweetrb/apple-mail-mcp/issues/147).
1445
+ - Nothing to configure — restart your host app after upgrading so it re-reads the tool list.
1446
+
1430
1447
  ### `apple-mail` server fails to connect when run from a clone
1431
1448
  - The root `.mcp.json` resolves its entrypoint via `${CLAUDE_PROJECT_DIR:-.}/build/index.js`. **Launch `claude` from inside the repo directory** — `CLAUDE_PROJECT_DIR` only resolves to the repo root in that case; the bare `.` fallback uses the launching shell's working directory and will point at the wrong place otherwise.
1432
1449
  - If you've been editing the source, rerun `npm run build` — the server is `build/index.js`, and the committed bundle only reflects your changes after a rebuild.
package/build/index.js CHANGED
@@ -83329,6 +83329,117 @@ function isOrphaned(ppid = process.ppid) {
83329
83329
  return ppid === 1;
83330
83330
  }
83331
83331
 
83332
+ // src/utils/jsonSchemaDialect.ts
83333
+ var JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
83334
+ var DEFINITIONS_REF_PREFIX = "#/definitions/";
83335
+ var SCHEMA_MAP_KEYWORDS = /* @__PURE__ */ new Set([
83336
+ "properties",
83337
+ "patternProperties",
83338
+ "$defs",
83339
+ "dependentSchemas"
83340
+ ]);
83341
+ var DATA_KEYWORDS = /* @__PURE__ */ new Set([
83342
+ "enum",
83343
+ "const",
83344
+ "default",
83345
+ "examples",
83346
+ "required",
83347
+ "dependentRequired"
83348
+ ]);
83349
+ function isPlainObject3(value) {
83350
+ return typeof value === "object" && value !== null && !Array.isArray(value);
83351
+ }
83352
+ function convertSchemaMap(node) {
83353
+ if (!isPlainObject3(node)) return node;
83354
+ const out = {};
83355
+ for (const [name, subschema] of Object.entries(node)) out[name] = convertNode(subschema);
83356
+ return out;
83357
+ }
83358
+ function convertNode(node) {
83359
+ if (Array.isArray(node)) return node.map(convertNode);
83360
+ if (!isPlainObject3(node)) return node;
83361
+ const hasTupleItems = Array.isArray(node.items);
83362
+ const out = {};
83363
+ for (const [key, value] of Object.entries(node)) {
83364
+ switch (key) {
83365
+ case "$schema":
83366
+ break;
83367
+ case "definitions":
83368
+ out.$defs = convertSchemaMap(value);
83369
+ break;
83370
+ case "$ref":
83371
+ out.$ref = typeof value === "string" && value.startsWith(DEFINITIONS_REF_PREFIX) ? "#/$defs/" + value.slice(DEFINITIONS_REF_PREFIX.length) : value;
83372
+ break;
83373
+ case "items":
83374
+ if (hasTupleItems) out.prefixItems = value.map(convertNode);
83375
+ else out.items = convertNode(value);
83376
+ break;
83377
+ case "additionalItems":
83378
+ if (hasTupleItems) out.items = convertNode(value);
83379
+ break;
83380
+ case "dependencies": {
83381
+ const dependentRequired = {};
83382
+ const dependentSchemas = {};
83383
+ if (isPlainObject3(value)) {
83384
+ for (const [property, dependency] of Object.entries(value)) {
83385
+ if (Array.isArray(dependency)) dependentRequired[property] = dependency;
83386
+ else dependentSchemas[property] = convertNode(dependency);
83387
+ }
83388
+ }
83389
+ if (Object.keys(dependentRequired).length > 0) out.dependentRequired = dependentRequired;
83390
+ if (Object.keys(dependentSchemas).length > 0) out.dependentSchemas = dependentSchemas;
83391
+ break;
83392
+ }
83393
+ case "exclusiveMinimum":
83394
+ case "exclusiveMaximum": {
83395
+ const bound = key === "exclusiveMinimum" ? node.minimum : node.maximum;
83396
+ if (value === true && typeof bound === "number") out[key] = bound;
83397
+ else if (value !== false) out[key] = convertNode(value);
83398
+ break;
83399
+ }
83400
+ case "minimum":
83401
+ if (node.exclusiveMinimum === true) break;
83402
+ out.minimum = convertNode(value);
83403
+ break;
83404
+ case "maximum":
83405
+ if (node.exclusiveMaximum === true) break;
83406
+ out.maximum = convertNode(value);
83407
+ break;
83408
+ default:
83409
+ if (DATA_KEYWORDS.has(key)) out[key] = value;
83410
+ else if (SCHEMA_MAP_KEYWORDS.has(key)) out[key] = convertSchemaMap(value);
83411
+ else out[key] = convertNode(value);
83412
+ }
83413
+ }
83414
+ return out;
83415
+ }
83416
+ function toJsonSchema2020_12(schema) {
83417
+ if (!isPlainObject3(schema)) return schema;
83418
+ return {
83419
+ $schema: JSON_SCHEMA_2020_12,
83420
+ ...convertNode(schema)
83421
+ };
83422
+ }
83423
+ function normalizeOutgoingMessage(message) {
83424
+ if (!isPlainObject3(message)) return message;
83425
+ const result = message.result;
83426
+ if (!isPlainObject3(result) || !Array.isArray(result.tools)) return message;
83427
+ const tools = result.tools.map((tool) => {
83428
+ if (!isPlainObject3(tool)) return tool;
83429
+ const next = { ...tool };
83430
+ if (isPlainObject3(tool.inputSchema)) next.inputSchema = toJsonSchema2020_12(tool.inputSchema);
83431
+ if (isPlainObject3(tool.outputSchema))
83432
+ next.outputSchema = toJsonSchema2020_12(tool.outputSchema);
83433
+ return next;
83434
+ });
83435
+ return { ...message, result: { ...result, tools } };
83436
+ }
83437
+ function withJsonSchema2020_12(transport2) {
83438
+ const originalSend = transport2.send.bind(transport2);
83439
+ transport2.send = (message, options) => originalSend(normalizeOutgoingMessage(message), options);
83440
+ return transport2;
83441
+ }
83442
+
83332
83443
  // src/index.ts
83333
83444
  loadFileConfig();
83334
83445
  var MESSAGE_ID_SCHEMA = external_exports.string().regex(/^(\d+|imap:[A-Za-z0-9_-]+)$/, "Message ID must be numeric or an IMAP id (imap:\u2026)");
@@ -85569,7 +85680,7 @@ process.on("uncaughtException", (err) => {
85569
85680
  process.on("unhandledRejection", (reason) => {
85570
85681
  console.error("[unhandledRejection]", reason);
85571
85682
  });
85572
- var transport = new StdioServerTransport();
85683
+ var transport = withJsonSchema2020_12(new StdioServerTransport());
85573
85684
  await server.connect(transport);
85574
85685
  var idleWatcher;
85575
85686
  if (/^(1|true|yes|on)$/i.test(process.env.APPLE_MAIL_MCP_IMAP_IDLE?.trim() ?? "")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.10.11",
3
+ "version": "2.10.13",
4
4
  "description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -54,6 +54,7 @@
54
54
  "@typescript-eslint/eslint-plugin": "^8.0.0",
55
55
  "@typescript-eslint/parser": "^8.0.0",
56
56
  "@vitest/coverage-v8": "^4.1.9",
57
+ "ajv": "^8.17.1",
57
58
  "esbuild": "^0.28.1",
58
59
  "eslint": "^9.0.0",
59
60
  "globals": "^17.0.0",