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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +2 -2
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/dist/apple-keys.js +22 -18
- package/dist/bundle.js +171 -49
- package/dist/calendar/tools.js +5 -1
- package/dist/music/tools-write.js +5 -2
- package/dist/tools/_confirm.js +3 -0
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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. |
|
|
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
|
|
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.
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -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 |
|
|
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@
|
|
116
|
+
### @chrischall/mcp-utils@3.0.0
|
|
117
117
|
|
|
118
118
|
- License: MIT
|
|
119
119
|
- Source: https://github.com/chrischall/mcp-utils
|
package/dist/apple-keys.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
80
|
+
const own = OWN_VARS[service];
|
|
77
81
|
const teamId = readEnvVar('APPLE_TEAM_ID', { env });
|
|
78
|
-
const ownKeyId = readEnvVar(
|
|
79
|
-
const ownKey = readEnvVar(
|
|
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 =
|
|
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 ${
|
|
105
|
+
missing.push(`APPLE_KEY_ID (or ${own.keyId})`);
|
|
102
106
|
if (rawKey === undefined)
|
|
103
|
-
missing.push(`APPLE_PRIVATE_KEY (or ${
|
|
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 ?
|
|
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
|
|
134
|
-
const ownKeyId = readEnvVar(
|
|
135
|
-
const ownKey = readEnvVar(
|
|
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, `${
|
|
146
|
-
'a key id must name the key that signs the token.', [
|
|
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, `${
|
|
150
|
-
'a key id must name the key that signs the token.', [
|
|
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 =
|
|
36114
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
36517
|
-
|
|
36518
|
-
|
|
36519
|
-
|
|
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
|
|
36524
|
-
|
|
36525
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
|
37066
|
+
const own2 = OWN_VARS[service];
|
|
36956
37067
|
const teamId = readEnvVar("APPLE_TEAM_ID", { env });
|
|
36957
|
-
const ownKeyId = readEnvVar(
|
|
36958
|
-
const ownKey = readEnvVar(
|
|
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 =
|
|
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 ${
|
|
36976
|
-
if (rawKey === void 0) missing.push(`APPLE_PRIVATE_KEY (or ${
|
|
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 ?
|
|
37112
|
+
source: `${ownKeyId ? own2.keyId : "APPLE_KEY_ID"} + ${keyVar}`
|
|
37002
37113
|
};
|
|
37003
37114
|
}
|
|
37004
37115
|
function assertPairMatches(service, env) {
|
|
37005
|
-
const
|
|
37006
|
-
const ownKeyId = readEnvVar(
|
|
37007
|
-
const ownKey = readEnvVar(
|
|
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
|
-
`${
|
|
37015
|
-
[
|
|
37016
|
-
`Set ${
|
|
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
|
-
`${
|
|
37023
|
-
[
|
|
37024
|
-
`Set ${
|
|
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
|
|
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
|
-
|
|
37076
|
-
music: "
|
|
37077
|
-
maps: "
|
|
37078
|
-
weather: "
|
|
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:
|
|
37762
|
+
default: DEFAULT_REQUEST_TIMEOUT_MS2,
|
|
37652
37763
|
min: 1e3,
|
|
37653
37764
|
max: MAX_REQUEST_TIMEOUT_MS
|
|
37654
37765
|
});
|
|
37655
|
-
return v ??
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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");
|
package/dist/calendar/tools.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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');
|
package/dist/tools/_confirm.js
CHANGED
|
@@ -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.
|
|
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
|
+
"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": "^
|
|
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.
|
|
9
|
+
"version": "0.3.4",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "apple-icloud-mcp",
|
|
14
|
-
"version": "0.3.
|
|
14
|
+
"version": "0.3.4",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|