apple-icloud-mcp 0.3.3 → 0.3.4

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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Unofficial Apple Music, iCloud Calendar/Contacts/Mail, Apple Maps and WeatherKit tools for Claude Code",
9
- "version": "0.3.3"
9
+ "version": "0.3.4"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -14,7 +14,7 @@
14
14
  "displayName": "Apple iCloud",
15
15
  "source": "./",
16
16
  "description": "Apple Music playlists and library, iCloud Calendar, Contacts and Mail, Apple Maps, WeatherKit and iTunes search for Claude via MCP — runs anywhere, no Mac required. Unofficial; not affiliated with Apple.",
17
- "version": "0.3.3",
17
+ "version": "0.3.4",
18
18
  "author": {
19
19
  "name": "Chris Chall"
20
20
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "apple-icloud",
3
3
  "displayName": "Apple iCloud",
4
- "version": "0.3.3",
4
+ "version": "0.3.4",
5
5
  "description": "Apple Music playlists and library, iCloud Calendar, Contacts and Mail, Apple Maps, WeatherKit and iTunes search for Claude via MCP — runs anywhere, no Mac required. Unofficial; not affiliated with Apple.",
6
6
  "author": {
7
7
  "name": "Chris Chall"
@@ -21,5 +21,5 @@
21
21
  "mcp"
22
22
  ],
23
23
  "skills": "./skills/",
24
- "mcp": "./.mcp.json"
24
+ "mcpServers": "./.mcp.json"
25
25
  }
package/README.md CHANGED
@@ -270,7 +270,7 @@ read in `DISPLAY_TZ` — so are calendar events stored without a time zone of th
270
270
  | `apple_music_get_ratings` | Whether you have loved or disliked songs, albums, playlists, music videos or stations — catalog or library ids — returning love, dislike or none per id. | read | |
271
271
  | `apple_music_create_playlist` | Create a new playlist in your Apple Music library, optionally with tracks (up to 500 catalog or library song ids; added 100 at a time), a description, a folder and public visibility (not in APPLE_WRITE_MODE=additive). | additive | |
272
272
  | `apple_music_add_playlist_tracks` | Append songs (up to 500 catalog or library ids) to the end of one of your library playlists. | additive | |
273
- | `apple_music_create_folder` | Create a playlist folder in your Apple Music library, at the top level or inside another folder. | additive | |
273
+ | `apple_music_create_folder` | Create a playlist folder in your Apple Music library, at the top level or inside another folder. | all | |
274
274
  | `apple_music_add_to_library` | Add catalog songs, albums, playlists or music videos to your Apple Music library by catalog id (up to 100 per type). | additive | |
275
275
  | `apple_music_add_favorites` | Mark catalog songs, albums, playlists, artists or music videos as favorites (the star in Apple Music; favorite songs go to your Favorite Songs playlist), by catalog id, up to 100 per type. | additive | |
276
276
  | `apple_music_set_rating` | Set your rating on a song, album, playlist, music video or station (catalog or library id): love, dislike, or none to clear it. | all | |
@@ -351,7 +351,7 @@ read in `DISPLAY_TZ` — so are calendar events stored without a time zone of th
351
351
  | Value | What is registered |
352
352
  |---|---|
353
353
  | `none` | Read tools only. |
354
- | `additive` | Reads, plus writes that only **add** to your own account: create a (private) playlist or folder, append tracks, add to library/favorites, create an event (without attendees, and not in a calendar shared with other people) or a contact. Nothing existing is modified or removed and nothing is sent to anyone. |
354
+ | `additive` | Reads, plus writes that only **add** to your own account: create a (private) playlist, append tracks, add to library/favorites, create an event (without attendees, and not in a calendar shared with other people) or a contact. Nothing existing is modified or removed and nothing is sent to anyone. |
355
355
  | `all` (default) | Everything. |
356
356
 
357
357
  Gated tools are not registered at all below their mode, so no prompt or injected instruction can call them.
@@ -22,7 +22,7 @@ MPL-2.0 is a file-level license: it covers ical.js's own files and does not exte
22
22
 
23
23
  | Package | Version | License | Source |
24
24
  |---|---|---|---|
25
- | @chrischall/mcp-utils | 2.15.0 | MIT | https://github.com/chrischall/mcp-utils |
25
+ | @chrischall/mcp-utils | 3.0.0 | MIT | https://github.com/chrischall/mcp-utils |
26
26
  | @esbuild/aix-ppc64 | 0.28.2 | MIT | — |
27
27
  | @esbuild/android-arm | 0.28.2 | MIT | — |
28
28
  | @esbuild/android-arm64 | 0.28.2 | MIT | — |
@@ -113,7 +113,7 @@ MPL-2.0 is a file-level license: it covers ical.js's own files and does not exte
113
113
 
114
114
  ## License and notice texts
115
115
 
116
- ### @chrischall/mcp-utils@2.15.0
116
+ ### @chrischall/mcp-utils@3.0.0
117
117
 
118
118
  - License: MIT
119
119
  - Source: https://github.com/chrischall/mcp-utils
@@ -2,10 +2,14 @@ import { createPrivateKey } from 'node:crypto';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { expandPath, readEnvVar, signEs256Jwt } from '@chrischall/mcp-utils';
4
4
  import { ConfigError, rememberSecret } from './errors.js';
5
- const PREFIX = {
6
- music: 'APPLE_MUSIC',
7
- maps: 'APPLE_MAPS',
8
- weather: 'APPLE_WEATHERKIT',
5
+ /**
6
+ * Each service's own key pair, spelled out in full (not built from a name
7
+ * stem) so every variable the server reads appears literally in the code.
8
+ */
9
+ const OWN_VARS = {
10
+ music: { keyId: 'APPLE_MUSIC_KEY_ID', privateKey: 'APPLE_MUSIC_PRIVATE_KEY' },
11
+ maps: { keyId: 'APPLE_MAPS_KEY_ID', privateKey: 'APPLE_MAPS_PRIVATE_KEY' },
12
+ weather: { keyId: 'APPLE_WEATHERKIT_KEY_ID', privateKey: 'APPLE_WEATHERKIT_PRIVATE_KEY' },
9
13
  };
10
14
  /**
11
15
  * Accept the shapes a .p8 actually arrives in through an env block:
@@ -73,16 +77,16 @@ function readKeyFile(path, varName, service) {
73
77
  */
74
78
  export function resolveDeveloperKey(service, env = process.env) {
75
79
  const svcName = service;
76
- const prefix = PREFIX[service];
80
+ const own = OWN_VARS[service];
77
81
  const teamId = readEnvVar('APPLE_TEAM_ID', { env });
78
- const ownKeyId = readEnvVar(`${prefix}_KEY_ID`, { env });
79
- const ownKey = readEnvVar(`${prefix}_PRIVATE_KEY`, { env });
82
+ const ownKeyId = readEnvVar(own.keyId, { env });
83
+ const ownKey = readEnvVar(own.privateKey, { env });
80
84
  const keyId = ownKeyId ?? readEnvVar('APPLE_KEY_ID', { env });
81
85
  let rawKey;
82
86
  let keyVar;
83
87
  if (ownKey !== undefined) {
84
88
  rawKey = ownKey;
85
- keyVar = `${prefix}_PRIVATE_KEY`;
89
+ keyVar = own.privateKey;
86
90
  }
87
91
  else if (readEnvVar('APPLE_PRIVATE_KEY', { env }) !== undefined) {
88
92
  rawKey = readEnvVar('APPLE_PRIVATE_KEY', { env });
@@ -98,9 +102,9 @@ export function resolveDeveloperKey(service, env = process.env) {
98
102
  if (!teamId)
99
103
  missing.push('APPLE_TEAM_ID');
100
104
  if (!keyId)
101
- missing.push(`APPLE_KEY_ID (or ${prefix}_KEY_ID)`);
105
+ missing.push(`APPLE_KEY_ID (or ${own.keyId})`);
102
106
  if (rawKey === undefined)
103
- missing.push(`APPLE_PRIVATE_KEY (or ${prefix}_PRIVATE_KEY / APPLE_PRIVATE_KEY_PATH)`);
107
+ missing.push(`APPLE_PRIVATE_KEY (or ${own.privateKey} / APPLE_PRIVATE_KEY_PATH)`);
104
108
  if (missing.length === 0)
105
109
  assertPairMatches(service, env);
106
110
  if (missing.length > 0) {
@@ -116,7 +120,7 @@ export function resolveDeveloperKey(service, env = process.env) {
116
120
  teamId: teamId,
117
121
  keyId: keyId,
118
122
  privateKeyPem: pem,
119
- source: `${ownKeyId ? `${prefix}_KEY_ID` : 'APPLE_KEY_ID'} + ${keyVar}`,
123
+ source: `${ownKeyId ? own.keyId : 'APPLE_KEY_ID'} + ${keyVar}`,
120
124
  };
121
125
  }
122
126
  /**
@@ -130,9 +134,9 @@ export function resolveDeveloperKey(service, env = process.env) {
130
134
  * APPLE_PRIVATE_KEY for one Music key) and is allowed.
131
135
  */
132
136
  function assertPairMatches(service, env) {
133
- const prefix = PREFIX[service];
134
- const ownKeyId = readEnvVar(`${prefix}_KEY_ID`, { env });
135
- const ownKey = readEnvVar(`${prefix}_PRIVATE_KEY`, { env });
137
+ const own = OWN_VARS[service];
138
+ const ownKeyId = readEnvVar(own.keyId, { env });
139
+ const ownKey = readEnvVar(own.privateKey, { env });
136
140
  const sharedKeyId = readEnvVar('APPLE_KEY_ID', { env });
137
141
  const sharedKeyVar = readEnvVar('APPLE_PRIVATE_KEY', { env }) !== undefined
138
142
  ? 'APPLE_PRIVATE_KEY'
@@ -142,12 +146,12 @@ function assertPairMatches(service, env) {
142
146
  if (sharedKeyId === undefined || sharedKeyVar === undefined)
143
147
  return;
144
148
  if (ownKey !== undefined && ownKeyId === undefined) {
145
- throw new ConfigError(service, `${prefix}_PRIVATE_KEY is set but ${prefix}_KEY_ID is not, and APPLE_KEY_ID belongs to the shared key in ${sharedKeyVar} — ` +
146
- 'a key id must name the key that signs the token.', [`${prefix}_KEY_ID`], `Set ${prefix}_KEY_ID to the Key ID of the key in ${prefix}_PRIVATE_KEY (Apple Developer portal → Keys), or unset ${prefix}_PRIVATE_KEY to use the shared key.`);
149
+ throw new ConfigError(service, `${own.privateKey} is set but ${own.keyId} is not, and APPLE_KEY_ID belongs to the shared key in ${sharedKeyVar} — ` +
150
+ 'a key id must name the key that signs the token.', [own.keyId], `Set ${own.keyId} to the Key ID of the key in ${own.privateKey} (Apple Developer portal → Keys), or unset ${own.privateKey} to use the shared key.`);
147
151
  }
148
152
  if (ownKeyId !== undefined && ownKey === undefined && ownKeyId !== sharedKeyId) {
149
- throw new ConfigError(service, `${prefix}_KEY_ID is set but ${prefix}_PRIVATE_KEY is not, and the shared key in ${sharedKeyVar} belongs to APPLE_KEY_ID — ` +
150
- 'a key id must name the key that signs the token.', [`${prefix}_PRIVATE_KEY`], `Set ${prefix}_PRIVATE_KEY to the .p8 contents of key ${prefix}_KEY_ID, or unset ${prefix}_KEY_ID to use the shared key.`);
153
+ throw new ConfigError(service, `${own.keyId} is set but ${own.privateKey} is not, and the shared key in ${sharedKeyVar} belongs to APPLE_KEY_ID — ` +
154
+ 'a key id must name the key that signs the token.', [own.privateKey], `Set ${own.privateKey} to the .p8 contents of key ${own.keyId}, or unset ${own.keyId} to use the shared key.`);
151
155
  }
152
156
  }
153
157
  function serviceLabel(service) {
package/dist/bundle.js CHANGED
@@ -35348,17 +35348,30 @@ function redactJsonCookie(quote) {
35348
35348
  function redactSecrets(text2) {
35349
35349
  return text2.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(GOOGLE_OAUTH_TOKEN_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_COOKIE_DQ_RE, redactJsonCookie('"')).replace(JSON_COOKIE_SQ_RE, redactJsonCookie("'")).replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_NUM_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
35350
35350
  }
35351
+ function messageOf(err) {
35352
+ if (err instanceof Error)
35353
+ return err.message;
35354
+ return String(err);
35355
+ }
35351
35356
  var McpToolError, HEADER_NAME_RUN, BEARER_RE, JWT_RE, BASIC_AUTH_RE, SET_COOKIE_RE, COOKIE_HEADER_RE, API_KEY_RE, GOOGLE_OAUTH_TOKEN_RE, QUERY_SECRET_RE, OAUTH_CODE_RE, FORM_PASSWORD_RE, HEADER_SECRET_RE, AWS_SIGV4_RE, JSON_SECRET_KEYS, JSON_SECRET_DQ_RE, JSON_SECRET_SQ_RE, JSON_SECRET_NUM_RE, JSON_COOKIE_DQ_RE, JSON_COOKIE_SQ_RE, DQ_STRING_RE, SQ_STRING_RE, ERROR_REDACTION_INPUT_MAX;
35352
35357
  var init_errors3 = __esm({
35353
35358
  "node_modules/@chrischall/mcp-utils/dist/errors/index.js"() {
35354
35359
  McpToolError = class extends Error {
35355
35360
  /** Actionable remediation text, when one applies. */
35356
35361
  hint;
35362
+ /** The upstream HTTP status this failure came from, when known. */
35363
+ status;
35364
+ /** What kind of failure this is, when the thrower knows. */
35365
+ kind;
35357
35366
  constructor(message, opts) {
35358
35367
  super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
35359
35368
  this.name = "McpToolError";
35360
35369
  if (opts?.hint !== void 0)
35361
35370
  this.hint = opts.hint;
35371
+ if (opts?.status !== void 0)
35372
+ this.status = opts.status;
35373
+ if (opts?.kind !== void 0)
35374
+ this.kind = opts.kind;
35362
35375
  Object.setPrototypeOf(this, new.target.prototype);
35363
35376
  }
35364
35377
  };
@@ -35383,7 +35396,7 @@ var init_errors3 = __esm({
35383
35396
  // webhook signing secret (Stripe-style)
35384
35397
  ].map((p) => `\\b${p}`).join("|"), "g");
35385
35398
  GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
35386
- QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
35399
+ QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
35387
35400
  OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
35388
35401
  FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
35389
35402
  HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
@@ -35394,6 +35407,8 @@ var init_errors3 = __esm({
35394
35407
  "api[_-]?secret",
35395
35408
  "(?:x[_-])?api[_-]?key",
35396
35409
  "private[_-]?key",
35410
+ "code[_-]?verifier",
35411
+ // PKCE; a bare `verifier` key is too generic for JSON
35397
35412
  `x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
35398
35413
  // bounded like HEADER_SECRET_RE
35399
35414
  "(?:proxy-)?authorization",
@@ -35872,7 +35887,7 @@ function readEnvVar(key, opts = {}) {
35872
35887
  if (typeof raw === "string") {
35873
35888
  const trimmed = raw.trim();
35874
35889
  if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
35875
- return trimmed;
35890
+ return opts.trim === false ? raw : trimmed;
35876
35891
  }
35877
35892
  }
35878
35893
  return opts.default;
@@ -36106,12 +36121,19 @@ function boundArgs(args) {
36106
36121
  }
36107
36122
  function confirmationFromEnv(options) {
36108
36123
  const { tool, account, confirmToken, subject, instruction, args, env = process.env, spent: callerSpent, ...rest } = options;
36124
+ if (args === void 0 || args === null) {
36125
+ throw new TypeError(`confirmationFromEnv: ${tool} passed no args; pass the tool's validated arguments ({} for a tool with none).`);
36126
+ }
36109
36127
  const spent = callerSpent ?? spentTokenStoreFromEnv(env);
36110
36128
  const mode = readConfirmMode(env);
36111
36129
  const ttlSeconds = readConfirmTtl(env);
36112
36130
  const elicitationOff = readConfirmElicitation(env) === "off";
36113
- const bound = args === void 0 ? void 0 : boundArgs(args);
36114
- const bindingAdded = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
36131
+ const bound = boundArgs(args);
36132
+ const elicitationBound = account === void 0 ? { args: bound } : { account, args: bound };
36133
+ const bindingAdded = rest.binding ? rest : {
36134
+ ...rest,
36135
+ binding: { key: confirmKeyFromEnv(env), args: elicitationBound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 }
36136
+ };
36115
36137
  const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
36116
36138
  if (mode === "refuse" || ttlSeconds === void 0) {
36117
36139
  const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
@@ -36121,7 +36143,7 @@ function confirmationFromEnv(options) {
36121
36143
  unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
36122
36144
  };
36123
36145
  }
36124
- const boundSubject = bound === void 0 ? subject : async () => {
36146
+ const boundSubject = async () => {
36125
36147
  const read = await subject();
36126
36148
  if ("content" in read)
36127
36149
  return read;
@@ -36174,12 +36196,14 @@ var init_merge_write = __esm({
36174
36196
 
36175
36197
  // node_modules/@chrischall/mcp-utils/dist/server/index.js
36176
36198
  function hintResultOrRethrow(err) {
36199
+ if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
36200
+ throw err;
36177
36201
  if (err instanceof McpToolError && err.hint) {
36178
36202
  return errorResult(`${err.message}
36179
36203
 
36180
36204
  Hint: ${err.hint}`);
36181
36205
  }
36182
- throw err;
36206
+ return errorResult(messageOf(err));
36183
36207
  }
36184
36208
  function mcpRequestFrom(args) {
36185
36209
  const ctx = args.at(-1);
@@ -36398,6 +36422,7 @@ var init_write = __esm({
36398
36422
  var init_output = __esm({
36399
36423
  "node_modules/@chrischall/mcp-utils/dist/fs/output.js"() {
36400
36424
  init_config();
36425
+ init_errors3();
36401
36426
  init_confine();
36402
36427
  init_write();
36403
36428
  }
@@ -36469,6 +36494,7 @@ var init_scrape = __esm({
36469
36494
  // node_modules/@chrischall/mcp-utils/dist/http/throttle.js
36470
36495
  var init_throttle = __esm({
36471
36496
  "node_modules/@chrischall/mcp-utils/dist/http/throttle.js"() {
36497
+ init_cancel();
36472
36498
  }
36473
36499
  });
36474
36500
 
@@ -36477,6 +36503,8 @@ function createResponseCache(opts) {
36477
36503
  const now = opts.now ?? Date.now;
36478
36504
  const maxEntries = opts.maxEntries ?? RESPONSE_CACHE_MAX_ENTRIES;
36479
36505
  const store2 = /* @__PURE__ */ new Map();
36506
+ const inflight2 = /* @__PURE__ */ new Map();
36507
+ let generation = 0;
36480
36508
  const ttlFor = (tier) => opts.ttlMs[tier] ?? 0;
36481
36509
  function evictForInsert() {
36482
36510
  if (store2.size < maxEntries)
@@ -36513,25 +36541,77 @@ function createResponseCache(opts) {
36513
36541
  store2.delete(key);
36514
36542
  store2.set(key, { expiresAt: now() + ttl, value });
36515
36543
  }
36516
- return {
36517
- get,
36518
- set: set2,
36519
- async fetchThrough(key, load, tier = "dynamic") {
36544
+ function startLoad(key, load, tier, signal) {
36545
+ const gen = generation;
36546
+ const shared = (async () => load())().then((value) => {
36547
+ if (gen === generation)
36548
+ set2(key, value, tier);
36549
+ return value;
36550
+ });
36551
+ const entry = { promise: shared, signal };
36552
+ inflight2.set(key, entry);
36553
+ const drop = () => {
36554
+ if (inflight2.get(key) === entry)
36555
+ inflight2.delete(key);
36556
+ };
36557
+ shared.then(drop, drop);
36558
+ return entry;
36559
+ }
36560
+ async function fetchThrough(key, load, tier = "dynamic", options = {}) {
36561
+ const { signal } = options;
36562
+ for (; ; ) {
36563
+ signal?.throwIfAborted();
36520
36564
  const hit = get(key);
36521
36565
  if (hit !== void 0)
36522
36566
  return hit;
36523
- const value = await load();
36524
- set2(key, value, tier);
36525
- return value;
36526
- },
36567
+ const pending = inflight2.get(key);
36568
+ if (!pending)
36569
+ return withSignal(startLoad(key, load, tier, signal).promise, signal);
36570
+ try {
36571
+ return await withSignal(pending.promise, signal);
36572
+ } catch (err) {
36573
+ const cancelled = pending.signal?.aborted === true || isCancellation(err);
36574
+ if (cancelled && !signal?.aborted)
36575
+ continue;
36576
+ throw err;
36577
+ }
36578
+ }
36579
+ }
36580
+ return {
36581
+ get,
36582
+ set: set2,
36583
+ fetchThrough,
36527
36584
  clear() {
36528
36585
  store2.clear();
36586
+ inflight2.clear();
36587
+ generation += 1;
36529
36588
  },
36530
36589
  get size() {
36531
36590
  return store2.size;
36532
36591
  }
36533
36592
  };
36534
36593
  }
36594
+ function withSignal(promise2, signal) {
36595
+ if (!signal)
36596
+ return promise2;
36597
+ return new Promise((resolve6, reject) => {
36598
+ const onAbort = () => reject(signal.reason);
36599
+ signal.addEventListener("abort", onAbort, { once: true });
36600
+ promise2.then((v) => {
36601
+ signal.removeEventListener("abort", onAbort);
36602
+ resolve6(v);
36603
+ }, (e) => {
36604
+ signal.removeEventListener("abort", onAbort);
36605
+ reject(e);
36606
+ });
36607
+ });
36608
+ }
36609
+ function isCancellation(err) {
36610
+ if (typeof err !== "object" || err === null)
36611
+ return false;
36612
+ const name2 = err.name;
36613
+ return name2 === "AbortError" || name2 === "CancelledError" || name2 === "CanceledError";
36614
+ }
36535
36615
  var RESPONSE_CACHE_MAX_ENTRIES;
36536
36616
  var init_response_cache = __esm({
36537
36617
  "node_modules/@chrischall/mcp-utils/dist/http/response-cache.js"() {
@@ -36555,6 +36635,31 @@ var init_net_atoms = __esm({
36555
36635
  }
36556
36636
  });
36557
36637
 
36638
+ // node_modules/@chrischall/mcp-utils/dist/http/body-limit.js
36639
+ var init_body_limit = __esm({
36640
+ "node_modules/@chrischall/mcp-utils/dist/http/body-limit.js"() {
36641
+ init_errors3();
36642
+ }
36643
+ });
36644
+
36645
+ // node_modules/@chrischall/mcp-utils/dist/http/fetch-bounded.js
36646
+ var init_fetch_bounded = __esm({
36647
+ "node_modules/@chrischall/mcp-utils/dist/http/fetch-bounded.js"() {
36648
+ init_cancel();
36649
+ init_errors3();
36650
+ init_body_limit();
36651
+ init_http();
36652
+ }
36653
+ });
36654
+
36655
+ // node_modules/@chrischall/mcp-utils/dist/http/url-safety.js
36656
+ var init_url_safety = __esm({
36657
+ "node_modules/@chrischall/mcp-utils/dist/http/url-safety.js"() {
36658
+ init_errors3();
36659
+ init_config();
36660
+ }
36661
+ });
36662
+
36558
36663
  // node_modules/@chrischall/mcp-utils/dist/internal/retry-after.js
36559
36664
  var init_retry_after = __esm({
36560
36665
  "node_modules/@chrischall/mcp-utils/dist/internal/retry-after.js"() {
@@ -36597,7 +36702,11 @@ var init_http = __esm({
36597
36702
  init_throttle();
36598
36703
  init_response_cache();
36599
36704
  init_net_atoms();
36705
+ init_fetch_bounded();
36706
+ init_url_safety();
36707
+ init_body_limit();
36600
36708
  init_net_atoms();
36709
+ init_url_safety();
36601
36710
  init_retry_after();
36602
36711
  EDGE_BLOCK_SCAN_MAX = 8 * 1024;
36603
36712
  EDGE_BLOCK_MARKERS = [
@@ -36662,6 +36771,7 @@ var init_concurrency = __esm({
36662
36771
  // node_modules/@chrischall/mcp-utils/dist/dates/index.js
36663
36772
  var init_dates = __esm({
36664
36773
  "node_modules/@chrischall/mcp-utils/dist/dates/index.js"() {
36774
+ init_config();
36665
36775
  }
36666
36776
  });
36667
36777
 
@@ -36695,7 +36805,7 @@ var init_zod2 = __esm({
36695
36805
  NonNegInt = external_exports.number().int().nonnegative();
36696
36806
  NonEmptyString = external_exports.string().min(1);
36697
36807
  IsoDate = external_exports.iso.date();
36698
- IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
36808
+ IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30").overwrite((v) => v.padStart(5, "0"));
36699
36809
  NumericIdString = external_exports.string().regex(/^\d+$/, "must be a numeric id (digits only)").describe("A numeric id string (digits only), safe to interpolate into a URL path.");
36700
36810
  SafePathSegment = external_exports.string().min(1).regex(/^[^/?#\s]+$/, 'must not contain "/", "?", "#", or whitespace').refine((v) => !v.includes(".."), 'must not contain ".."').describe('A single URL path segment, safe to interpolate: no "/", "..", "?", "#", or whitespace.');
36701
36811
  schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
@@ -36774,6 +36884,7 @@ var init_auth = __esm({
36774
36884
  init_config();
36775
36885
  init_errors3();
36776
36886
  init_http();
36887
+ init_cancel();
36777
36888
  init_cached_token();
36778
36889
  init_es256();
36779
36890
  }
@@ -36803,7 +36914,7 @@ var VERSION;
36803
36914
  var init_version = __esm({
36804
36915
  "src/version.ts"() {
36805
36916
  "use strict";
36806
- VERSION = "0.3.3";
36917
+ VERSION = "0.3.4";
36807
36918
  }
36808
36919
  });
36809
36920
 
@@ -36824,7 +36935,7 @@ function errorMessage(err) {
36824
36935
  if (err instanceof Error) return scrub(err.message);
36825
36936
  return scrub(String(err));
36826
36937
  }
36827
- var AppleToolError, ConfigError, CredentialsRejectedError, UpstreamError, ResponseTooLargeError, TransportError, InvalidArgumentError, UnconfirmedWriteError, secretLiterals, PEM_PRIVATE_KEY_RE;
36938
+ var AppleToolError, ConfigError, CredentialsRejectedError, UpstreamError, ResponseTooLargeError2, TransportError, InvalidArgumentError, UnconfirmedWriteError, secretLiterals, PEM_PRIVATE_KEY_RE;
36828
36939
  var init_errors4 = __esm({
36829
36940
  "src/errors.ts"() {
36830
36941
  "use strict";
@@ -36873,7 +36984,7 @@ var init_errors4 = __esm({
36873
36984
  if (opts.upstreamCode !== void 0) this.upstreamCode = opts.upstreamCode;
36874
36985
  }
36875
36986
  };
36876
- ResponseTooLargeError = class extends UpstreamError {
36987
+ ResponseTooLargeError2 = class extends UpstreamError {
36877
36988
  constructor(service, status2, message) {
36878
36989
  super(service, status2, message);
36879
36990
  this.name = "ResponseTooLargeError";
@@ -36952,16 +37063,16 @@ function readKeyFile(path2, varName, service) {
36952
37063
  }
36953
37064
  function resolveDeveloperKey(service, env = process.env) {
36954
37065
  const svcName = service;
36955
- const prefix = PREFIX2[service];
37066
+ const own2 = OWN_VARS[service];
36956
37067
  const teamId = readEnvVar("APPLE_TEAM_ID", { env });
36957
- const ownKeyId = readEnvVar(`${prefix}_KEY_ID`, { env });
36958
- const ownKey = readEnvVar(`${prefix}_PRIVATE_KEY`, { env });
37068
+ const ownKeyId = readEnvVar(own2.keyId, { env });
37069
+ const ownKey = readEnvVar(own2.privateKey, { env });
36959
37070
  const keyId = ownKeyId ?? readEnvVar("APPLE_KEY_ID", { env });
36960
37071
  let rawKey;
36961
37072
  let keyVar;
36962
37073
  if (ownKey !== void 0) {
36963
37074
  rawKey = ownKey;
36964
- keyVar = `${prefix}_PRIVATE_KEY`;
37075
+ keyVar = own2.privateKey;
36965
37076
  } else if (readEnvVar("APPLE_PRIVATE_KEY", { env }) !== void 0) {
36966
37077
  rawKey = readEnvVar("APPLE_PRIVATE_KEY", { env });
36967
37078
  keyVar = "APPLE_PRIVATE_KEY";
@@ -36972,8 +37083,8 @@ function resolveDeveloperKey(service, env = process.env) {
36972
37083
  }
36973
37084
  const missing = [];
36974
37085
  if (!teamId) missing.push("APPLE_TEAM_ID");
36975
- if (!keyId) missing.push(`APPLE_KEY_ID (or ${prefix}_KEY_ID)`);
36976
- if (rawKey === void 0) missing.push(`APPLE_PRIVATE_KEY (or ${prefix}_PRIVATE_KEY / APPLE_PRIVATE_KEY_PATH)`);
37086
+ if (!keyId) missing.push(`APPLE_KEY_ID (or ${own2.keyId})`);
37087
+ if (rawKey === void 0) missing.push(`APPLE_PRIVATE_KEY (or ${own2.privateKey} / APPLE_PRIVATE_KEY_PATH)`);
36977
37088
  if (missing.length === 0) assertPairMatches(service, env);
36978
37089
  if (missing.length > 0) {
36979
37090
  throw new ConfigError(
@@ -36998,30 +37109,30 @@ function resolveDeveloperKey(service, env = process.env) {
36998
37109
  teamId,
36999
37110
  keyId,
37000
37111
  privateKeyPem: pem,
37001
- source: `${ownKeyId ? `${prefix}_KEY_ID` : "APPLE_KEY_ID"} + ${keyVar}`
37112
+ source: `${ownKeyId ? own2.keyId : "APPLE_KEY_ID"} + ${keyVar}`
37002
37113
  };
37003
37114
  }
37004
37115
  function assertPairMatches(service, env) {
37005
- const prefix = PREFIX2[service];
37006
- const ownKeyId = readEnvVar(`${prefix}_KEY_ID`, { env });
37007
- const ownKey = readEnvVar(`${prefix}_PRIVATE_KEY`, { env });
37116
+ const own2 = OWN_VARS[service];
37117
+ const ownKeyId = readEnvVar(own2.keyId, { env });
37118
+ const ownKey = readEnvVar(own2.privateKey, { env });
37008
37119
  const sharedKeyId = readEnvVar("APPLE_KEY_ID", { env });
37009
37120
  const sharedKeyVar = readEnvVar("APPLE_PRIVATE_KEY", { env }) !== void 0 ? "APPLE_PRIVATE_KEY" : readEnvVar("APPLE_PRIVATE_KEY_PATH", { env }) !== void 0 ? "APPLE_PRIVATE_KEY_PATH" : void 0;
37010
37121
  if (sharedKeyId === void 0 || sharedKeyVar === void 0) return;
37011
37122
  if (ownKey !== void 0 && ownKeyId === void 0) {
37012
37123
  throw new ConfigError(
37013
37124
  service,
37014
- `${prefix}_PRIVATE_KEY is set but ${prefix}_KEY_ID is not, and APPLE_KEY_ID belongs to the shared key in ${sharedKeyVar} \u2014 a key id must name the key that signs the token.`,
37015
- [`${prefix}_KEY_ID`],
37016
- `Set ${prefix}_KEY_ID to the Key ID of the key in ${prefix}_PRIVATE_KEY (Apple Developer portal \u2192 Keys), or unset ${prefix}_PRIVATE_KEY to use the shared key.`
37125
+ `${own2.privateKey} is set but ${own2.keyId} is not, and APPLE_KEY_ID belongs to the shared key in ${sharedKeyVar} \u2014 a key id must name the key that signs the token.`,
37126
+ [own2.keyId],
37127
+ `Set ${own2.keyId} to the Key ID of the key in ${own2.privateKey} (Apple Developer portal \u2192 Keys), or unset ${own2.privateKey} to use the shared key.`
37017
37128
  );
37018
37129
  }
37019
37130
  if (ownKeyId !== void 0 && ownKey === void 0 && ownKeyId !== sharedKeyId) {
37020
37131
  throw new ConfigError(
37021
37132
  service,
37022
- `${prefix}_KEY_ID is set but ${prefix}_PRIVATE_KEY is not, and the shared key in ${sharedKeyVar} belongs to APPLE_KEY_ID \u2014 a key id must name the key that signs the token.`,
37023
- [`${prefix}_PRIVATE_KEY`],
37024
- `Set ${prefix}_PRIVATE_KEY to the .p8 contents of key ${prefix}_KEY_ID, or unset ${prefix}_KEY_ID to use the shared key.`
37133
+ `${own2.keyId} is set but ${own2.privateKey} is not, and the shared key in ${sharedKeyVar} belongs to APPLE_KEY_ID \u2014 a key id must name the key that signs the token.`,
37134
+ [own2.privateKey],
37135
+ `Set ${own2.privateKey} to the .p8 contents of key ${own2.keyId}, or unset ${own2.keyId} to use the shared key.`
37025
37136
  );
37026
37137
  }
37027
37138
  }
@@ -37066,16 +37177,16 @@ function decodeJwtPayload(token) {
37066
37177
  return void 0;
37067
37178
  }
37068
37179
  }
37069
- var PREFIX2, PEM_BLOCK_RE, MUSIC_TOKEN_TTL_S, MAPS_AUTH_TOKEN_TTL_S, WEATHERKIT_TOKEN_TTL_S;
37180
+ var OWN_VARS, PEM_BLOCK_RE, MUSIC_TOKEN_TTL_S, MAPS_AUTH_TOKEN_TTL_S, WEATHERKIT_TOKEN_TTL_S;
37070
37181
  var init_apple_keys = __esm({
37071
37182
  "src/apple-keys.ts"() {
37072
37183
  "use strict";
37073
37184
  init_dist2();
37074
37185
  init_errors4();
37075
- PREFIX2 = {
37076
- music: "APPLE_MUSIC",
37077
- maps: "APPLE_MAPS",
37078
- weather: "APPLE_WEATHERKIT"
37186
+ OWN_VARS = {
37187
+ music: { keyId: "APPLE_MUSIC_KEY_ID", privateKey: "APPLE_MUSIC_PRIVATE_KEY" },
37188
+ maps: { keyId: "APPLE_MAPS_KEY_ID", privateKey: "APPLE_MAPS_PRIVATE_KEY" },
37189
+ weather: { keyId: "APPLE_WEATHERKIT_KEY_ID", privateKey: "APPLE_WEATHERKIT_PRIVATE_KEY" }
37079
37190
  };
37080
37191
  PEM_BLOCK_RE = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/;
37081
37192
  MUSIC_TOKEN_TTL_S = 12 * 60 * 60;
@@ -37648,23 +37759,23 @@ function systemTimeZone() {
37648
37759
  function getRequestTimeoutMs(env = process.env) {
37649
37760
  const v = readIntEnv("APPLE_REQUEST_TIMEOUT_MS", {
37650
37761
  env,
37651
- default: DEFAULT_REQUEST_TIMEOUT_MS,
37762
+ default: DEFAULT_REQUEST_TIMEOUT_MS2,
37652
37763
  min: 1e3,
37653
37764
  max: MAX_REQUEST_TIMEOUT_MS
37654
37765
  });
37655
- return v ?? DEFAULT_REQUEST_TIMEOUT_MS;
37766
+ return v ?? DEFAULT_REQUEST_TIMEOUT_MS2;
37656
37767
  }
37657
37768
  function isDebugLog(env = process.env) {
37658
37769
  return parseBoolEnv("APPLE_DEBUG_LOG", { env });
37659
37770
  }
37660
- var SERVICES, warnedServices, WRITE_MODES, warnedWriteMode, warnedTz, DEFAULT_REQUEST_TIMEOUT_MS, MAX_REQUEST_TIMEOUT_MS;
37771
+ var SERVICES, warnedServices, WRITE_MODES, warnedWriteMode, warnedTz, DEFAULT_REQUEST_TIMEOUT_MS2, MAX_REQUEST_TIMEOUT_MS;
37661
37772
  var init_config2 = __esm({
37662
37773
  "src/config.ts"() {
37663
37774
  "use strict";
37664
37775
  init_dist2();
37665
37776
  SERVICES = ["music", "calendar", "contacts", "mail", "maps", "weather", "itunes"];
37666
37777
  WRITE_MODES = ["none", "additive", "all"];
37667
- DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
37778
+ DEFAULT_REQUEST_TIMEOUT_MS2 = 3e4;
37668
37779
  MAX_REQUEST_TIMEOUT_MS = 6e5;
37669
37780
  }
37670
37781
  });
@@ -38156,7 +38267,7 @@ async function httpRequest(req) {
38156
38267
  if (mayHaveLanded) {
38157
38268
  throw new UnconfirmedWriteError(req.service, `${what} (HTTP ${res.status}); the change may have been applied.`);
38158
38269
  }
38159
- throw new ResponseTooLargeError(req.service, res.status, `${what}.`);
38270
+ throw new ResponseTooLargeError2(req.service, res.status, `${what}.`);
38160
38271
  }
38161
38272
  const timedOut = controller.signal.aborted;
38162
38273
  const why = timedOut ? `timed out reading the response after ${timeoutMs} ms` : `lost the connection reading the response`;
@@ -38599,6 +38710,7 @@ var init_session = __esm({
38599
38710
  "node_modules/@chrischall/mcp-utils/dist/session/index.js"() {
38600
38711
  init_response();
38601
38712
  init_config();
38713
+ init_errors3();
38602
38714
  init_http();
38603
38715
  init_edge_block();
38604
38716
  init_login_page();
@@ -46160,6 +46272,9 @@ function confirmWrite(ctx, opts) {
46160
46272
  details: opts.preview,
46161
46273
  unsupportedNote: "Make this change in the Apple app (Music, Calendar, Contacts or Mail) instead.",
46162
46274
  tool: opts.tool,
46275
+ // Single-account server: every write acts as the one set of credentials in the env
46276
+ // (ICLOUD_USERNAME / the Apple Music user token), so there is no principal to choose between.
46277
+ account: void 0,
46163
46278
  confirmToken: opts.confirmToken,
46164
46279
  args: opts.args,
46165
46280
  // Built per call: the data dir, APPLE_STATE_CACHE and the key are read now.
@@ -56680,7 +56795,11 @@ function registerCalendarTools(server, deps = {}) {
56680
56795
  attendees: attendeesParam.optional().describe("People to invite. iCloud emails each one an invitation."),
56681
56796
  confirmToken: confirmTokenParam
56682
56797
  }),
56683
- annotations: ANNOTATIONS.additive,
56798
+ // With APPLE_WRITE_MODE=all it accepts attendees, and iCloud emails each an
56799
+ // invitation: that reaches another person, and apple_calendar_delete_event
56800
+ // cannot un-send it, so it is destructive there. APPLE_WRITE_MODE=additive
56801
+ // refuses attendees and shared calendars, leaving a plain, deletable addition.
56802
+ annotations: accessAllowed("all") ? ANNOTATIONS.send : ANNOTATIONS.additive,
56684
56803
  handler: async (args, ctx) => {
56685
56804
  const zones2 = resolveZones(args.timeZone);
56686
56805
  const { zone } = zones2;
@@ -57884,7 +58003,7 @@ async function downloadInBatches(session, into) {
57884
58003
  try {
57885
58004
  res = await session.client.report(session.bookUrl, addressbookMultigetBody(batch), 1);
57886
58005
  } catch (err) {
57887
- if (!(err instanceof ResponseTooLargeError)) throw err;
58006
+ if (!(err instanceof ResponseTooLargeError2)) throw err;
57888
58007
  if (batch.length === 1) {
57889
58008
  into.tooLarge += 1;
57890
58009
  return;
@@ -57912,7 +58031,7 @@ async function download(session, version4) {
57912
58031
  try {
57913
58032
  res = await session.client.report(session.bookUrl, addressbookQueryBody(ADDRESS_DATA_PROPS), 1);
57914
58033
  } catch (err) {
57915
- if (!(err instanceof ResponseTooLargeError)) throw err;
58034
+ if (!(err instanceof ResponseTooLargeError2)) throw err;
57916
58035
  }
57917
58036
  if (res) {
57918
58037
  into.unreadable += res.skipped;
@@ -122608,14 +122727,17 @@ function registerLibraryWriteTools(server, client) {
122608
122727
  defineTool(server, {
122609
122728
  name: "apple_music_create_folder",
122610
122729
  service: "music",
122611
- access: "additive",
122730
+ // No tool in this server removes a playlist folder (delete_playlist deletes playlists only), so a created
122731
+ // folder cannot be undone here: destructive by the inverse test, and so outside APPLE_WRITE_MODE=additive,
122732
+ // whose promise is that every write it registers is non-destructive.
122733
+ access: "all",
122612
122734
  title: "Create an Apple Music playlist folder",
122613
122735
  description: "Create a playlist folder in your Apple Music library, at the top level or inside another folder. Returns the new folder id (p.\u2026) for apple_music_create_playlist / apple_music_move_playlist. Needs APPLE_MUSIC_USER_TOKEN or web-player mode.",
122614
122736
  inputSchema: external_exports.strictObject({
122615
122737
  name: external_exports.string().trim().min(1).max(200).describe("Folder name."),
122616
122738
  parentFolderId: external_exports.string().min(1).max(140).optional().describe('Create it inside this folder (p.\u2026; default "root", the top level).')
122617
122739
  }),
122618
- annotations: ANNOTATIONS.additive,
122740
+ annotations: { ...ANNOTATIONS.additive, destructiveHint: true },
122619
122741
  handler: async (args) => {
122620
122742
  const parent = folderIdArg(args.parentFolderId ?? "root");
122621
122743
  assertLibraryPlaylistId(parent, "parentFolderId", "folder");
@@ -471,7 +471,11 @@ export function registerCalendarTools(server, deps = {}) {
471
471
  attendees: attendeesParam.optional().describe('People to invite. iCloud emails each one an invitation.'),
472
472
  confirmToken: confirmTokenParam,
473
473
  }),
474
- annotations: ANNOTATIONS.additive,
474
+ // With APPLE_WRITE_MODE=all it accepts attendees, and iCloud emails each an
475
+ // invitation: that reaches another person, and apple_calendar_delete_event
476
+ // cannot un-send it, so it is destructive there. APPLE_WRITE_MODE=additive
477
+ // refuses attendees and shared calendars, leaving a plain, deletable addition.
478
+ annotations: accessAllowed('all') ? ANNOTATIONS.send : ANNOTATIONS.additive,
475
479
  handler: async (args, ctx) => {
476
480
  const zones = resolveZones(args.timeZone);
477
481
  const { zone } = zones;
@@ -302,7 +302,10 @@ export function registerLibraryWriteTools(server, client) {
302
302
  defineTool(server, {
303
303
  name: 'apple_music_create_folder',
304
304
  service: 'music',
305
- access: 'additive',
305
+ // No tool in this server removes a playlist folder (delete_playlist deletes playlists only), so a created
306
+ // folder cannot be undone here: destructive by the inverse test, and so outside APPLE_WRITE_MODE=additive,
307
+ // whose promise is that every write it registers is non-destructive.
308
+ access: 'all',
306
309
  title: 'Create an Apple Music playlist folder',
307
310
  description: 'Create a playlist folder in your Apple Music library, at the top level or inside another folder. Returns the new ' +
308
311
  'folder id (p.…) for apple_music_create_playlist / apple_music_move_playlist. Needs APPLE_MUSIC_USER_TOKEN or web-player mode.',
@@ -310,7 +313,7 @@ export function registerLibraryWriteTools(server, client) {
310
313
  name: z.string().trim().min(1).max(200).describe('Folder name.'),
311
314
  parentFolderId: z.string().min(1).max(140).optional().describe('Create it inside this folder (p.…; default "root", the top level).'),
312
315
  }),
313
- annotations: ANNOTATIONS.additive,
316
+ annotations: { ...ANNOTATIONS.additive, destructiveHint: true },
314
317
  handler: async (args) => {
315
318
  const parent = folderIdArg(args.parentFolderId ?? 'root');
316
319
  assertLibraryPlaylistId(parent, 'parentFolderId', 'folder');
@@ -40,6 +40,9 @@ export function confirmWrite(ctx, opts) {
40
40
  details: opts.preview,
41
41
  unsupportedNote: 'Make this change in the Apple app (Music, Calendar, Contacts or Mail) instead.',
42
42
  tool: opts.tool,
43
+ // Single-account server: every write acts as the one set of credentials in the env
44
+ // (ICLOUD_USERNAME / the Apple Music user token), so there is no principal to choose between.
45
+ account: undefined,
43
46
  confirmToken: opts.confirmToken,
44
47
  args: opts.args,
45
48
  // Built per call: the data dir, APPLE_STATE_CACHE and the key are read now.
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // The server's self-reported version. release-please bumps this line (it is
2
2
  // registered in release-please-config.json `extra-files`); tests/version-sync
3
3
  // fails CI if it ever drifts from package.json.
4
- export const VERSION = '0.3.3'; // x-release-please-version
4
+ export const VERSION = '0.3.4'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-icloud-mcp",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "license": "MIT",
5
5
  "mcpName": "io.github.chrischall/apple-icloud-mcp",
6
6
  "description": "Unofficial MCP server for Apple's services — Apple Music playlists and library, iCloud Calendar, Contacts and Mail, Apple Maps, WeatherKit and iTunes search — that runs anywhere, no Mac needed. Not affiliated with Apple. Developed and maintained by AI (Claude Code)",
@@ -54,7 +54,7 @@
54
54
  "typecheck": "tsc -p tsconfig.json --noEmit"
55
55
  },
56
56
  "dependencies": {
57
- "@chrischall/mcp-utils": "^2.15.0",
57
+ "@chrischall/mcp-utils": "^3.0.0",
58
58
  "@modelcontextprotocol/server": "^2.3.0",
59
59
  "@xmldom/xmldom": "^0.9.12",
60
60
  "dotenv": "^18.0.5",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/apple-icloud-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.3.3",
9
+ "version": "0.3.4",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "apple-icloud-mcp",
14
- "version": "0.3.3",
14
+ "version": "0.3.4",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },