asc-mcp 1.0.0
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/LICENSE +21 -0
- package/README.md +463 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2813 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2813 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
|
|
8
|
+
// src/utils/errors.ts
|
|
9
|
+
function getSensitivePatterns() {
|
|
10
|
+
return [
|
|
11
|
+
/Bearer [A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/gi,
|
|
12
|
+
// JWT tokens
|
|
13
|
+
/-----BEGIN.*?-----[\s\S]*?-----END.*?-----/g,
|
|
14
|
+
// PEM keys
|
|
15
|
+
/[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}/gi
|
|
16
|
+
// UUIDs (issuer IDs)
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
function sanitizeMessage(message) {
|
|
20
|
+
let sanitized = message;
|
|
21
|
+
for (const pattern of getSensitivePatterns()) {
|
|
22
|
+
sanitized = sanitized.replace(pattern, "[REDACTED]");
|
|
23
|
+
}
|
|
24
|
+
return sanitized;
|
|
25
|
+
}
|
|
26
|
+
var ASCError = class extends Error {
|
|
27
|
+
code;
|
|
28
|
+
status;
|
|
29
|
+
details;
|
|
30
|
+
constructor(message, code, status, details) {
|
|
31
|
+
super(sanitizeMessage(message));
|
|
32
|
+
this.name = "ASCError";
|
|
33
|
+
this.code = code;
|
|
34
|
+
this.status = status;
|
|
35
|
+
this.details = details;
|
|
36
|
+
}
|
|
37
|
+
toJSON() {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: {
|
|
41
|
+
code: this.code,
|
|
42
|
+
message: this.message,
|
|
43
|
+
details: this.details
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var AuthError = class extends ASCError {
|
|
49
|
+
constructor(message, details) {
|
|
50
|
+
super(message, "AUTH_ERROR", 401, details);
|
|
51
|
+
this.name = "AuthError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var RateLimitError = class extends ASCError {
|
|
55
|
+
retryAfter;
|
|
56
|
+
constructor(retryAfter, details) {
|
|
57
|
+
super(`Rate limit exceeded. Retry after ${retryAfter} seconds.`, "RATE_LIMIT", 429, details);
|
|
58
|
+
this.name = "RateLimitError";
|
|
59
|
+
this.retryAfter = retryAfter;
|
|
60
|
+
}
|
|
61
|
+
toJSON() {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: {
|
|
65
|
+
code: this.code,
|
|
66
|
+
message: this.message,
|
|
67
|
+
details: this.details,
|
|
68
|
+
retryAfter: this.retryAfter
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var ValidationError = class extends ASCError {
|
|
74
|
+
field;
|
|
75
|
+
constructor(message, field) {
|
|
76
|
+
super(message, "VALIDATION_ERROR", 400);
|
|
77
|
+
this.name = "ValidationError";
|
|
78
|
+
this.field = field;
|
|
79
|
+
}
|
|
80
|
+
toJSON() {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: {
|
|
84
|
+
code: this.code,
|
|
85
|
+
message: this.message,
|
|
86
|
+
details: this.details,
|
|
87
|
+
field: this.field
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var ConfigError = class extends ASCError {
|
|
93
|
+
constructor(message) {
|
|
94
|
+
super(message, "CONFIG_ERROR", 500);
|
|
95
|
+
this.name = "ConfigError";
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
function parseAPIError(status, body) {
|
|
99
|
+
const errors = body?.errors;
|
|
100
|
+
if (status === 401) {
|
|
101
|
+
return new AuthError("Authentication failed", errors);
|
|
102
|
+
}
|
|
103
|
+
if (status === 429) {
|
|
104
|
+
return new RateLimitError(60, errors);
|
|
105
|
+
}
|
|
106
|
+
if (status === 404) {
|
|
107
|
+
const detail = errors?.[0]?.detail ?? "Resource not found";
|
|
108
|
+
return new ASCError(detail, "NOT_FOUND", 404, errors);
|
|
109
|
+
}
|
|
110
|
+
if (status === 403) {
|
|
111
|
+
return new ASCError(
|
|
112
|
+
"Access forbidden. Check your API key permissions.",
|
|
113
|
+
"FORBIDDEN",
|
|
114
|
+
403,
|
|
115
|
+
errors
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (status === 409) {
|
|
119
|
+
const detail = errors?.[0]?.detail ?? "Conflict with current state";
|
|
120
|
+
return new ASCError(detail, "CONFLICT", 409, errors);
|
|
121
|
+
}
|
|
122
|
+
const message = errors?.[0]?.detail ?? `API request failed with status ${status}`;
|
|
123
|
+
return new ASCError(message, "API_ERROR", status, errors);
|
|
124
|
+
}
|
|
125
|
+
function formatErrorResponse(error) {
|
|
126
|
+
if (error instanceof ASCError) {
|
|
127
|
+
return error.toJSON();
|
|
128
|
+
}
|
|
129
|
+
if (error instanceof Error) {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
error: {
|
|
133
|
+
code: "INTERNAL_ERROR",
|
|
134
|
+
message: sanitizeMessage(error.message)
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
error: {
|
|
141
|
+
code: "UNKNOWN_ERROR",
|
|
142
|
+
message: "An unknown error occurred"
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/api/client.ts
|
|
148
|
+
var BASE_URL = "https://api.appstoreconnect.apple.com/v1";
|
|
149
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
150
|
+
var MAX_RETRIES = 3;
|
|
151
|
+
var INITIAL_RETRY_DELAY_MS = 1e3;
|
|
152
|
+
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
153
|
+
var MAX_REQUESTS_PER_WINDOW = 50;
|
|
154
|
+
var AppStoreConnectClient = class {
|
|
155
|
+
tokenManager;
|
|
156
|
+
baseUrl;
|
|
157
|
+
rateLimiter = { timestamps: [] };
|
|
158
|
+
constructor(tokenManager, baseUrl = BASE_URL) {
|
|
159
|
+
this.tokenManager = tokenManager;
|
|
160
|
+
this.baseUrl = baseUrl;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Make a GET request
|
|
164
|
+
*/
|
|
165
|
+
async get(path2, params) {
|
|
166
|
+
return this.request({ method: "GET", path: path2, params });
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Make a POST request
|
|
170
|
+
*/
|
|
171
|
+
async post(path2, body) {
|
|
172
|
+
return this.request({ method: "POST", path: path2, body });
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Make a PATCH request
|
|
176
|
+
*/
|
|
177
|
+
async patch(path2, body) {
|
|
178
|
+
return this.request({ method: "PATCH", path: path2, body });
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Make a DELETE request
|
|
182
|
+
*/
|
|
183
|
+
async delete(path2, body) {
|
|
184
|
+
await this.request({ method: "DELETE", path: path2, body });
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Make a raw request (for custom operations like file uploads)
|
|
188
|
+
*/
|
|
189
|
+
async rawRequest(url, options) {
|
|
190
|
+
const controller = new AbortController();
|
|
191
|
+
const timeoutId = setTimeout(() => controller.abort(), options.timeout ?? DEFAULT_TIMEOUT_MS);
|
|
192
|
+
try {
|
|
193
|
+
const response = await fetch(url, {
|
|
194
|
+
method: options.method,
|
|
195
|
+
headers: options.headers,
|
|
196
|
+
body: options.body,
|
|
197
|
+
signal: controller.signal
|
|
198
|
+
});
|
|
199
|
+
return response;
|
|
200
|
+
} finally {
|
|
201
|
+
clearTimeout(timeoutId);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Paginate through all results
|
|
206
|
+
*/
|
|
207
|
+
async *paginate(path2, params, maxItems) {
|
|
208
|
+
let cursor;
|
|
209
|
+
let itemsYielded = 0;
|
|
210
|
+
do {
|
|
211
|
+
const queryParams = { ...params };
|
|
212
|
+
if (cursor) {
|
|
213
|
+
if (cursor.includes("?")) {
|
|
214
|
+
try {
|
|
215
|
+
const url = new URL(cursor);
|
|
216
|
+
for (const [key, value] of url.searchParams) {
|
|
217
|
+
queryParams[key] = value;
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
const queryString = cursor.split("?")[1];
|
|
221
|
+
if (queryString) {
|
|
222
|
+
const searchParams = new URLSearchParams(queryString);
|
|
223
|
+
for (const [key, value] of searchParams) {
|
|
224
|
+
queryParams[key] = value;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const response = await this.get(path2, queryParams);
|
|
231
|
+
for (const item of response.data) {
|
|
232
|
+
yield item;
|
|
233
|
+
itemsYielded++;
|
|
234
|
+
if (maxItems && itemsYielded >= maxItems) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
cursor = response.links?.next;
|
|
239
|
+
} while (cursor);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Core request method with retry logic
|
|
243
|
+
*/
|
|
244
|
+
async request(options) {
|
|
245
|
+
await this.enforceRateLimit();
|
|
246
|
+
let lastError;
|
|
247
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
248
|
+
try {
|
|
249
|
+
return await this.executeRequest(options);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
252
|
+
if (error instanceof ASCError && error.status >= 400 && error.status < 500) {
|
|
253
|
+
if (error instanceof RateLimitError) {
|
|
254
|
+
const waitMs = error.retryAfter * 1e3;
|
|
255
|
+
await this.sleep(waitMs);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
261
|
+
const delay = INITIAL_RETRY_DELAY_MS * 2 ** attempt;
|
|
262
|
+
await this.sleep(delay);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
throw lastError ?? new Error("Request failed after max retries");
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Execute a single HTTP request
|
|
270
|
+
*/
|
|
271
|
+
async executeRequest(options) {
|
|
272
|
+
const token = await this.tokenManager.getToken();
|
|
273
|
+
const url = this.buildUrl(options.path, options.params);
|
|
274
|
+
const headers = {
|
|
275
|
+
Authorization: `Bearer ${token}`
|
|
276
|
+
};
|
|
277
|
+
if (options.body) {
|
|
278
|
+
headers["Content-Type"] = "application/json";
|
|
279
|
+
}
|
|
280
|
+
const controller = new AbortController();
|
|
281
|
+
const timeoutId = setTimeout(() => controller.abort(), options.timeout ?? DEFAULT_TIMEOUT_MS);
|
|
282
|
+
try {
|
|
283
|
+
const response = await fetch(url, {
|
|
284
|
+
method: options.method,
|
|
285
|
+
headers,
|
|
286
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
287
|
+
signal: controller.signal
|
|
288
|
+
});
|
|
289
|
+
clearTimeout(timeoutId);
|
|
290
|
+
if (response.status === 429) {
|
|
291
|
+
const retryAfter = Number.parseInt(response.headers.get("Retry-After") ?? "60", 10);
|
|
292
|
+
throw new RateLimitError(retryAfter);
|
|
293
|
+
}
|
|
294
|
+
if (response.status === 204) {
|
|
295
|
+
return void 0;
|
|
296
|
+
}
|
|
297
|
+
const body = await response.json();
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
throw parseAPIError(response.status, body);
|
|
300
|
+
}
|
|
301
|
+
return body;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
clearTimeout(timeoutId);
|
|
304
|
+
if (error instanceof ASCError) {
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
if (error instanceof Error) {
|
|
308
|
+
if (error.name === "AbortError") {
|
|
309
|
+
throw new ASCError("Request timed out", "TIMEOUT", 408);
|
|
310
|
+
}
|
|
311
|
+
throw new ASCError(error.message, "NETWORK_ERROR", 0);
|
|
312
|
+
}
|
|
313
|
+
throw new ASCError("Unknown error occurred", "UNKNOWN", 0);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Build URL with query parameters
|
|
318
|
+
*/
|
|
319
|
+
buildUrl(path2, params) {
|
|
320
|
+
const url = new URL(path2.startsWith("/") ? path2.slice(1) : path2, `${this.baseUrl}/`);
|
|
321
|
+
if (params) {
|
|
322
|
+
for (const [key, value] of Object.entries(params)) {
|
|
323
|
+
if (value !== void 0) {
|
|
324
|
+
url.searchParams.append(key, String(value));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return url.toString();
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Self-imposed rate limiting.
|
|
332
|
+
* Records the request timestamp before checking to prevent off-by-one errors.
|
|
333
|
+
*/
|
|
334
|
+
async enforceRateLimit() {
|
|
335
|
+
const now = Date.now();
|
|
336
|
+
this.rateLimiter.timestamps = this.rateLimiter.timestamps.filter(
|
|
337
|
+
(ts) => now - ts < RATE_LIMIT_WINDOW_MS
|
|
338
|
+
);
|
|
339
|
+
if (this.rateLimiter.timestamps.length >= MAX_REQUESTS_PER_WINDOW) {
|
|
340
|
+
const oldestTimestamp = this.rateLimiter.timestamps[0];
|
|
341
|
+
if (oldestTimestamp) {
|
|
342
|
+
const waitTime = RATE_LIMIT_WINDOW_MS - (now - oldestTimestamp);
|
|
343
|
+
if (waitTime > 0) {
|
|
344
|
+
await this.sleep(waitTime);
|
|
345
|
+
const newNow = Date.now();
|
|
346
|
+
this.rateLimiter.timestamps = this.rateLimiter.timestamps.filter(
|
|
347
|
+
(ts) => newNow - ts < RATE_LIMIT_WINDOW_MS
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
this.rateLimiter.timestamps.push(Date.now());
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Sleep helper
|
|
356
|
+
*/
|
|
357
|
+
sleep(ms) {
|
|
358
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
function createClient(tokenManager) {
|
|
362
|
+
return new AppStoreConnectClient(tokenManager);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/auth/jwt.ts
|
|
366
|
+
import * as fs from "fs/promises";
|
|
367
|
+
import * as path from "path";
|
|
368
|
+
import { SignJWT, importPKCS8 } from "jose";
|
|
369
|
+
var TOKEN_LIFETIME_SECONDS = 15 * 60;
|
|
370
|
+
var TOKEN_REFRESH_BUFFER_SECONDS = 5 * 60;
|
|
371
|
+
var AUDIENCE = "appstoreconnect-v1";
|
|
372
|
+
var TokenManager = class {
|
|
373
|
+
keyId;
|
|
374
|
+
issuerId;
|
|
375
|
+
privateKeySource;
|
|
376
|
+
cachedToken = null;
|
|
377
|
+
tokenExpiresAt = 0;
|
|
378
|
+
privateKey = null;
|
|
379
|
+
pendingTokenGeneration = null;
|
|
380
|
+
constructor(config) {
|
|
381
|
+
this.keyId = config.keyId;
|
|
382
|
+
this.issuerId = config.issuerId;
|
|
383
|
+
if (config.privateKeyPath) {
|
|
384
|
+
if (config.privateKeyPath.includes("..")) {
|
|
385
|
+
throw new ConfigError("Private key path cannot contain '..'");
|
|
386
|
+
}
|
|
387
|
+
this.privateKeySource = { type: "path", path: config.privateKeyPath };
|
|
388
|
+
} else if (config.privateKeyContent) {
|
|
389
|
+
this.privateKeySource = { type: "content", content: config.privateKeyContent };
|
|
390
|
+
} else {
|
|
391
|
+
throw new ConfigError("Either privateKeyPath or privateKeyContent must be provided");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Get a valid JWT token, generating a new one if needed.
|
|
396
|
+
* Uses a pending promise pattern to prevent concurrent token generation.
|
|
397
|
+
*/
|
|
398
|
+
async getToken() {
|
|
399
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
400
|
+
if (this.cachedToken && this.tokenExpiresAt > now + TOKEN_REFRESH_BUFFER_SECONDS) {
|
|
401
|
+
return this.cachedToken;
|
|
402
|
+
}
|
|
403
|
+
if (this.pendingTokenGeneration) {
|
|
404
|
+
return this.pendingTokenGeneration;
|
|
405
|
+
}
|
|
406
|
+
this.pendingTokenGeneration = this.generateToken().finally(() => {
|
|
407
|
+
this.pendingTokenGeneration = null;
|
|
408
|
+
});
|
|
409
|
+
return this.pendingTokenGeneration;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Generate a new JWT token
|
|
413
|
+
*/
|
|
414
|
+
async generateToken() {
|
|
415
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
416
|
+
const expiresAt = now + TOKEN_LIFETIME_SECONDS;
|
|
417
|
+
if (!this.privateKey) {
|
|
418
|
+
await this.loadPrivateKey();
|
|
419
|
+
}
|
|
420
|
+
if (!this.privateKey) {
|
|
421
|
+
throw new AuthError("Failed to load private key");
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
const jwt = await new SignJWT({}).setProtectedHeader({
|
|
425
|
+
alg: "ES256",
|
|
426
|
+
kid: this.keyId,
|
|
427
|
+
typ: "JWT"
|
|
428
|
+
}).setIssuer(this.issuerId).setIssuedAt(now).setExpirationTime(expiresAt).setAudience(AUDIENCE).sign(this.privateKey);
|
|
429
|
+
this.cachedToken = jwt;
|
|
430
|
+
this.tokenExpiresAt = expiresAt;
|
|
431
|
+
return jwt;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
throw new AuthError(
|
|
434
|
+
`Failed to generate JWT: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Load the private key from file or content
|
|
440
|
+
*/
|
|
441
|
+
async loadPrivateKey() {
|
|
442
|
+
let keyContent;
|
|
443
|
+
if (this.privateKeySource.type === "path") {
|
|
444
|
+
try {
|
|
445
|
+
const absolutePath = path.resolve(this.privateKeySource.path);
|
|
446
|
+
keyContent = await fs.readFile(absolutePath, "utf-8");
|
|
447
|
+
} catch (error) {
|
|
448
|
+
throw new ConfigError(
|
|
449
|
+
`Failed to read private key from ${this.privateKeySource.path}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
} else {
|
|
453
|
+
keyContent = this.privateKeySource.content;
|
|
454
|
+
}
|
|
455
|
+
keyContent = keyContent.trim();
|
|
456
|
+
if (!keyContent.startsWith("-----BEGIN")) {
|
|
457
|
+
keyContent = `-----BEGIN PRIVATE KEY-----
|
|
458
|
+
${keyContent}
|
|
459
|
+
-----END PRIVATE KEY-----`;
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
this.privateKey = await importPKCS8(keyContent, "ES256");
|
|
463
|
+
} catch (error) {
|
|
464
|
+
throw new AuthError(
|
|
465
|
+
`Failed to parse private key: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Clear cached token (call on shutdown)
|
|
471
|
+
*/
|
|
472
|
+
destroy() {
|
|
473
|
+
this.cachedToken = null;
|
|
474
|
+
this.tokenExpiresAt = 0;
|
|
475
|
+
this.privateKey = null;
|
|
476
|
+
this.pendingTokenGeneration = null;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Check if a token is currently cached and valid
|
|
480
|
+
*/
|
|
481
|
+
hasValidToken() {
|
|
482
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
483
|
+
return this.cachedToken !== null && this.tokenExpiresAt > now + TOKEN_REFRESH_BUFFER_SECONDS;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
function createTokenManagerFromEnv() {
|
|
487
|
+
const keyId = process.env.APP_STORE_CONNECT_KEY_ID;
|
|
488
|
+
const issuerId = process.env.APP_STORE_CONNECT_ISSUER_ID;
|
|
489
|
+
const privateKeyPath = process.env.APP_STORE_CONNECT_P8_PATH;
|
|
490
|
+
const privateKeyContent = process.env.APP_STORE_CONNECT_P8_CONTENT;
|
|
491
|
+
if (!keyId) {
|
|
492
|
+
throw new ConfigError("APP_STORE_CONNECT_KEY_ID environment variable is required");
|
|
493
|
+
}
|
|
494
|
+
if (!issuerId) {
|
|
495
|
+
throw new ConfigError("APP_STORE_CONNECT_ISSUER_ID environment variable is required");
|
|
496
|
+
}
|
|
497
|
+
if (!privateKeyPath && !privateKeyContent) {
|
|
498
|
+
throw new ConfigError(
|
|
499
|
+
"Either APP_STORE_CONNECT_P8_PATH or APP_STORE_CONNECT_P8_CONTENT environment variable is required"
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
return new TokenManager({
|
|
503
|
+
keyId,
|
|
504
|
+
issuerId,
|
|
505
|
+
privateKeyPath,
|
|
506
|
+
privateKeyContent
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/utils/validation.ts
|
|
511
|
+
import { z } from "zod";
|
|
512
|
+
var appIdSchema = z.string().regex(/^\d+$/, "App ID must be a numeric string").min(1, "App ID is required");
|
|
513
|
+
var versionIdSchema = z.string().regex(/^\d+$/, "Version ID must be a numeric string").min(1, "Version ID is required");
|
|
514
|
+
var localizationIdSchema = z.string().regex(/^\d+$/, "Localization ID must be a numeric string").min(1, "Localization ID is required");
|
|
515
|
+
var buildIdSchema = z.string().min(1, "Build ID is required");
|
|
516
|
+
var betaGroupIdSchema = z.string().min(1, "Beta Group ID is required");
|
|
517
|
+
var localeSchema = z.string().regex(
|
|
518
|
+
/^[a-z]{2}([-_][A-Z][a-z]{3})?([-_][A-Z]{2})?$/,
|
|
519
|
+
"Invalid locale format (e.g., en-US, en_US, ja, zh-Hans)"
|
|
520
|
+
).min(2, "Locale is required");
|
|
521
|
+
var urlSchema = z.string().url("Must be a valid URL").refine((url) => url.startsWith("https://"), "URL must use HTTPS");
|
|
522
|
+
var optionalUrlSchema = urlSchema.optional();
|
|
523
|
+
var versionStringSchema = z.string().regex(/^(0|[1-9]\d*)(\.(0|[1-9]\d*))*$/, "Invalid version format (e.g., 1.0.0)").min(1, "Version string is required");
|
|
524
|
+
var platformSchema = z.enum(["IOS", "MAC_OS", "TV_OS", "VISION_OS"]);
|
|
525
|
+
var versionStateSchema = z.enum([
|
|
526
|
+
"DEVELOPER_REMOVED_FROM_SALE",
|
|
527
|
+
"DEVELOPER_REJECTED",
|
|
528
|
+
"IN_REVIEW",
|
|
529
|
+
"INVALID_BINARY",
|
|
530
|
+
"METADATA_REJECTED",
|
|
531
|
+
"PENDING_APPLE_RELEASE",
|
|
532
|
+
"PENDING_CONTRACT",
|
|
533
|
+
"PENDING_DEVELOPER_RELEASE",
|
|
534
|
+
"PREPARE_FOR_SUBMISSION",
|
|
535
|
+
"PREORDER_READY_FOR_SALE",
|
|
536
|
+
"PROCESSING_FOR_APP_STORE",
|
|
537
|
+
"READY_FOR_REVIEW",
|
|
538
|
+
"READY_FOR_SALE",
|
|
539
|
+
"REJECTED",
|
|
540
|
+
"REMOVED_FROM_SALE",
|
|
541
|
+
"WAITING_FOR_EXPORT_COMPLIANCE",
|
|
542
|
+
"WAITING_FOR_REVIEW",
|
|
543
|
+
"REPLACED_WITH_NEW_VERSION",
|
|
544
|
+
"NOT_APPLICABLE"
|
|
545
|
+
]);
|
|
546
|
+
var releaseTypeSchema = z.enum(["MANUAL", "AFTER_APPROVAL", "SCHEDULED"]);
|
|
547
|
+
var paginationSchema = z.object({
|
|
548
|
+
limit: z.number().int().min(1).max(200).optional().default(50),
|
|
549
|
+
cursor: z.string().optional()
|
|
550
|
+
});
|
|
551
|
+
var listAppsInputSchema = z.object({
|
|
552
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
553
|
+
});
|
|
554
|
+
var getAppInputSchema = z.object({
|
|
555
|
+
appId: appIdSchema
|
|
556
|
+
});
|
|
557
|
+
var listAppVersionsInputSchema = z.object({
|
|
558
|
+
appId: appIdSchema,
|
|
559
|
+
platform: platformSchema.optional(),
|
|
560
|
+
versionState: versionStateSchema.optional(),
|
|
561
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
562
|
+
});
|
|
563
|
+
var getAppVersionInputSchema = z.object({
|
|
564
|
+
versionId: versionIdSchema
|
|
565
|
+
});
|
|
566
|
+
var createAppVersionInputSchema = z.object({
|
|
567
|
+
appId: appIdSchema,
|
|
568
|
+
platform: platformSchema,
|
|
569
|
+
versionString: versionStringSchema,
|
|
570
|
+
releaseType: releaseTypeSchema.optional(),
|
|
571
|
+
copyright: z.string().optional(),
|
|
572
|
+
earliestReleaseDate: z.string().datetime().optional()
|
|
573
|
+
});
|
|
574
|
+
var listVersionLocalizationsInputSchema = z.object({
|
|
575
|
+
versionId: versionIdSchema,
|
|
576
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
577
|
+
});
|
|
578
|
+
var getVersionLocalizationInputSchema = z.object({
|
|
579
|
+
localizationId: localizationIdSchema
|
|
580
|
+
});
|
|
581
|
+
var createVersionLocalizationInputSchema = z.object({
|
|
582
|
+
versionId: versionIdSchema,
|
|
583
|
+
locale: localeSchema,
|
|
584
|
+
description: z.string().max(4e3).optional(),
|
|
585
|
+
keywords: z.string().max(100).optional(),
|
|
586
|
+
whatsNew: z.string().max(4e3).optional(),
|
|
587
|
+
promotionalText: z.string().max(170).optional(),
|
|
588
|
+
marketingUrl: optionalUrlSchema,
|
|
589
|
+
supportUrl: optionalUrlSchema
|
|
590
|
+
});
|
|
591
|
+
var updateVersionLocalizationInputSchema = z.object({
|
|
592
|
+
localizationId: localizationIdSchema,
|
|
593
|
+
description: z.string().max(4e3).optional(),
|
|
594
|
+
keywords: z.string().max(100).optional(),
|
|
595
|
+
whatsNew: z.string().max(4e3).optional(),
|
|
596
|
+
promotionalText: z.string().max(170).optional(),
|
|
597
|
+
marketingUrl: optionalUrlSchema,
|
|
598
|
+
supportUrl: optionalUrlSchema
|
|
599
|
+
});
|
|
600
|
+
var deleteVersionLocalizationInputSchema = z.object({
|
|
601
|
+
localizationId: localizationIdSchema
|
|
602
|
+
});
|
|
603
|
+
var getAppInfosInputSchema = z.object({
|
|
604
|
+
appId: appIdSchema,
|
|
605
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
606
|
+
});
|
|
607
|
+
var listAppInfoLocalizationsInputSchema = z.object({
|
|
608
|
+
appInfoId: z.string().min(1, "App Info ID is required"),
|
|
609
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
610
|
+
});
|
|
611
|
+
var updateAppInfoLocalizationInputSchema = z.object({
|
|
612
|
+
localizationId: localizationIdSchema,
|
|
613
|
+
name: z.string().max(30).optional(),
|
|
614
|
+
subtitle: z.string().max(30).optional(),
|
|
615
|
+
privacyPolicyUrl: optionalUrlSchema,
|
|
616
|
+
privacyChoicesUrl: optionalUrlSchema,
|
|
617
|
+
privacyPolicyText: z.string().optional()
|
|
618
|
+
});
|
|
619
|
+
var listBetaGroupsInputSchema = z.object({
|
|
620
|
+
appId: appIdSchema,
|
|
621
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
622
|
+
});
|
|
623
|
+
var listBetaTestersInputSchema = z.object({
|
|
624
|
+
betaGroupId: betaGroupIdSchema,
|
|
625
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
626
|
+
});
|
|
627
|
+
var addBetaTesterInputSchema = z.object({
|
|
628
|
+
betaGroupId: betaGroupIdSchema,
|
|
629
|
+
email: z.string().email("Invalid email address"),
|
|
630
|
+
firstName: z.string().min(1).optional(),
|
|
631
|
+
lastName: z.string().min(1).optional()
|
|
632
|
+
});
|
|
633
|
+
var listScreenshotSetsInputSchema = z.object({
|
|
634
|
+
localizationId: localizationIdSchema,
|
|
635
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
636
|
+
});
|
|
637
|
+
var listScreenshotsInputSchema = z.object({
|
|
638
|
+
screenshotSetId: z.string().min(1, "Screenshot Set ID is required"),
|
|
639
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
640
|
+
});
|
|
641
|
+
var uploadScreenshotInputSchema = z.object({
|
|
642
|
+
screenshotSetId: z.string().min(1, "Screenshot Set ID is required"),
|
|
643
|
+
fileName: z.string().min(1, "File name is required"),
|
|
644
|
+
fileSize: z.number().int().positive("File size must be positive"),
|
|
645
|
+
filePath: z.string().min(1, "File path is required")
|
|
646
|
+
});
|
|
647
|
+
function validateInput(schema, input) {
|
|
648
|
+
const result = schema.safeParse(input);
|
|
649
|
+
if (!result.success) {
|
|
650
|
+
const firstError = result.error.errors[0];
|
|
651
|
+
const field = firstError?.path.join(".");
|
|
652
|
+
const message = firstError?.message ?? "Invalid input";
|
|
653
|
+
throw new ValidationError(message, field || void 0);
|
|
654
|
+
}
|
|
655
|
+
return result.data;
|
|
656
|
+
}
|
|
657
|
+
var bundleIdIdentifierSchema = z.string().regex(
|
|
658
|
+
/^[a-zA-Z][a-zA-Z0-9.-]*$/,
|
|
659
|
+
"Bundle ID must start with a letter and contain only letters, numbers, dots, and hyphens"
|
|
660
|
+
).min(1, "Bundle ID identifier is required");
|
|
661
|
+
var listBundleIdsInputSchema = z.object({
|
|
662
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
663
|
+
platform: platformSchema.optional()
|
|
664
|
+
});
|
|
665
|
+
var getBundleIdInputSchema = z.object({
|
|
666
|
+
bundleIdId: z.string().min(1, "Bundle ID ID is required")
|
|
667
|
+
});
|
|
668
|
+
var createBundleIdInputSchema = z.object({
|
|
669
|
+
identifier: bundleIdIdentifierSchema,
|
|
670
|
+
name: z.string().min(1, "Name is required").max(255, "Name must be 255 characters or less"),
|
|
671
|
+
platform: platformSchema
|
|
672
|
+
});
|
|
673
|
+
var updateBundleIdInputSchema = z.object({
|
|
674
|
+
bundleIdId: z.string().min(1, "Bundle ID ID is required"),
|
|
675
|
+
name: z.string().min(1, "Name is required").max(255, "Name must be 255 characters or less")
|
|
676
|
+
});
|
|
677
|
+
var deleteBundleIdInputSchema = z.object({
|
|
678
|
+
bundleIdId: z.string().min(1, "Bundle ID ID is required")
|
|
679
|
+
});
|
|
680
|
+
var deviceStatusSchema = z.enum(["ENABLED", "DISABLED"]);
|
|
681
|
+
var listDevicesInputSchema = z.object({
|
|
682
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
683
|
+
platform: platformSchema.optional(),
|
|
684
|
+
status: deviceStatusSchema.optional()
|
|
685
|
+
});
|
|
686
|
+
var getDeviceInputSchema = z.object({
|
|
687
|
+
deviceId: z.string().min(1, "Device ID is required")
|
|
688
|
+
});
|
|
689
|
+
var userRoleSchema = z.enum([
|
|
690
|
+
"ADMIN",
|
|
691
|
+
"FINANCE",
|
|
692
|
+
"TECHNICAL",
|
|
693
|
+
"SALES",
|
|
694
|
+
"DEVELOPER",
|
|
695
|
+
"MARKETING",
|
|
696
|
+
"APP_MANAGER",
|
|
697
|
+
"CUSTOMER_SUPPORT",
|
|
698
|
+
"ACCESS_TO_REPORTS",
|
|
699
|
+
"READ_ONLY"
|
|
700
|
+
]);
|
|
701
|
+
var listUsersInputSchema = z.object({
|
|
702
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
703
|
+
roles: z.array(userRoleSchema).optional()
|
|
704
|
+
});
|
|
705
|
+
var getUserInputSchema = z.object({
|
|
706
|
+
userId: z.string().min(1, "User ID is required")
|
|
707
|
+
});
|
|
708
|
+
var betaTesterIdSchema = z.string().min(1, "Beta Tester ID is required");
|
|
709
|
+
var removeBetaTesterInputSchema = z.object({
|
|
710
|
+
betaGroupId: betaGroupIdSchema,
|
|
711
|
+
betaTesterId: betaTesterIdSchema
|
|
712
|
+
});
|
|
713
|
+
var listBuildsInputSchema = z.object({
|
|
714
|
+
appId: appIdSchema,
|
|
715
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
716
|
+
});
|
|
717
|
+
var getBuildInputSchema = z.object({
|
|
718
|
+
buildId: buildIdSchema
|
|
719
|
+
});
|
|
720
|
+
var listBetaTesterInvitationsInputSchema = z.object({
|
|
721
|
+
betaGroupId: betaGroupIdSchema,
|
|
722
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
723
|
+
});
|
|
724
|
+
var listAppCategoriesInputSchema = z.object({
|
|
725
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
726
|
+
platform: platformSchema.optional()
|
|
727
|
+
});
|
|
728
|
+
var getAppPriceScheduleInputSchema = z.object({
|
|
729
|
+
appId: appIdSchema
|
|
730
|
+
});
|
|
731
|
+
var getAppAvailabilityInputSchema = z.object({
|
|
732
|
+
appId: appIdSchema
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// src/tools/app-info.tools.ts
|
|
736
|
+
async function listAppInfos(client, input) {
|
|
737
|
+
try {
|
|
738
|
+
const params = validateInput(getAppInfosInputSchema, input);
|
|
739
|
+
const response = await client.get(`/apps/${params.appId}/appInfos`, {
|
|
740
|
+
limit: params.limit,
|
|
741
|
+
"fields[appInfos]": "appStoreState,appStoreAgeRating"
|
|
742
|
+
});
|
|
743
|
+
return {
|
|
744
|
+
success: true,
|
|
745
|
+
data: response.data.map((info) => ({
|
|
746
|
+
id: info.id,
|
|
747
|
+
appStoreState: info.attributes.appStoreState,
|
|
748
|
+
appStoreAgeRating: info.attributes.appStoreAgeRating
|
|
749
|
+
})),
|
|
750
|
+
meta: {
|
|
751
|
+
total: response.meta?.paging?.total,
|
|
752
|
+
returned: response.data.length
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
} catch (error) {
|
|
756
|
+
return formatErrorResponse(error);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
async function listAppInfoLocalizations(client, input) {
|
|
760
|
+
try {
|
|
761
|
+
const params = validateInput(listAppInfoLocalizationsInputSchema, input);
|
|
762
|
+
const response = await client.get(
|
|
763
|
+
`/appInfos/${params.appInfoId}/appInfoLocalizations`,
|
|
764
|
+
{
|
|
765
|
+
limit: params.limit,
|
|
766
|
+
"fields[appInfoLocalizations]": "locale,name,subtitle,privacyPolicyUrl,privacyChoicesUrl,privacyPolicyText"
|
|
767
|
+
}
|
|
768
|
+
);
|
|
769
|
+
return {
|
|
770
|
+
success: true,
|
|
771
|
+
data: response.data.map((loc) => ({
|
|
772
|
+
id: loc.id,
|
|
773
|
+
locale: loc.attributes.locale,
|
|
774
|
+
name: loc.attributes.name,
|
|
775
|
+
subtitle: loc.attributes.subtitle,
|
|
776
|
+
privacyPolicyUrl: loc.attributes.privacyPolicyUrl,
|
|
777
|
+
privacyChoicesUrl: loc.attributes.privacyChoicesUrl,
|
|
778
|
+
privacyPolicyText: loc.attributes.privacyPolicyText
|
|
779
|
+
})),
|
|
780
|
+
meta: {
|
|
781
|
+
total: response.meta?.paging?.total,
|
|
782
|
+
returned: response.data.length
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
} catch (error) {
|
|
786
|
+
return formatErrorResponse(error);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
async function updateAppInfoLocalization(client, input) {
|
|
790
|
+
try {
|
|
791
|
+
const params = validateInput(updateAppInfoLocalizationInputSchema, input);
|
|
792
|
+
const requestBody = {
|
|
793
|
+
data: {
|
|
794
|
+
type: "appInfoLocalizations",
|
|
795
|
+
id: params.localizationId,
|
|
796
|
+
attributes: {
|
|
797
|
+
name: params.name,
|
|
798
|
+
subtitle: params.subtitle,
|
|
799
|
+
privacyPolicyUrl: params.privacyPolicyUrl,
|
|
800
|
+
privacyChoicesUrl: params.privacyChoicesUrl,
|
|
801
|
+
privacyPolicyText: params.privacyPolicyText
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
const response = await client.patch(
|
|
806
|
+
`/appInfoLocalizations/${params.localizationId}`,
|
|
807
|
+
requestBody
|
|
808
|
+
);
|
|
809
|
+
const loc = response.data;
|
|
810
|
+
return {
|
|
811
|
+
success: true,
|
|
812
|
+
data: {
|
|
813
|
+
id: loc.id,
|
|
814
|
+
locale: loc.attributes.locale,
|
|
815
|
+
name: loc.attributes.name,
|
|
816
|
+
subtitle: loc.attributes.subtitle,
|
|
817
|
+
privacyPolicyUrl: loc.attributes.privacyPolicyUrl,
|
|
818
|
+
privacyChoicesUrl: loc.attributes.privacyChoicesUrl,
|
|
819
|
+
privacyPolicyText: loc.attributes.privacyPolicyText
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
} catch (error) {
|
|
823
|
+
return formatErrorResponse(error);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
var appInfoToolDefinitions = [
|
|
827
|
+
{
|
|
828
|
+
name: "list_app_infos",
|
|
829
|
+
description: "List app info records for an app. Use this to get the appInfoId needed for localization operations.",
|
|
830
|
+
inputSchema: {
|
|
831
|
+
type: "object",
|
|
832
|
+
properties: {
|
|
833
|
+
appId: {
|
|
834
|
+
type: "string",
|
|
835
|
+
description: "The App Store Connect app ID"
|
|
836
|
+
},
|
|
837
|
+
limit: {
|
|
838
|
+
type: "number",
|
|
839
|
+
description: "Maximum number of results to return (1-200)",
|
|
840
|
+
minimum: 1,
|
|
841
|
+
maximum: 200
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
required: ["appId"]
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
name: "list_app_info_localizations",
|
|
849
|
+
description: "List all localizations for an app info. Returns app name, subtitle, and privacy policy info for each locale.",
|
|
850
|
+
inputSchema: {
|
|
851
|
+
type: "object",
|
|
852
|
+
properties: {
|
|
853
|
+
appInfoId: {
|
|
854
|
+
type: "string",
|
|
855
|
+
description: "The App Info ID (get this from list_app_infos)"
|
|
856
|
+
},
|
|
857
|
+
limit: {
|
|
858
|
+
type: "number",
|
|
859
|
+
description: "Maximum number of localizations to return (1-200)",
|
|
860
|
+
minimum: 1,
|
|
861
|
+
maximum: 200
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
required: ["appInfoId"]
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
name: "update_app_info_localization",
|
|
869
|
+
description: "Update an app info localization. Use this to change app name, subtitle, or privacy policy URL for a locale.",
|
|
870
|
+
inputSchema: {
|
|
871
|
+
type: "object",
|
|
872
|
+
properties: {
|
|
873
|
+
localizationId: {
|
|
874
|
+
type: "string",
|
|
875
|
+
description: "The app info localization ID to update"
|
|
876
|
+
},
|
|
877
|
+
name: {
|
|
878
|
+
type: "string",
|
|
879
|
+
description: "App name (max 30 characters)"
|
|
880
|
+
},
|
|
881
|
+
subtitle: {
|
|
882
|
+
type: "string",
|
|
883
|
+
description: "App subtitle (max 30 characters)"
|
|
884
|
+
},
|
|
885
|
+
privacyPolicyUrl: {
|
|
886
|
+
type: "string",
|
|
887
|
+
description: "Privacy policy URL (HTTPS only)"
|
|
888
|
+
},
|
|
889
|
+
privacyChoicesUrl: {
|
|
890
|
+
type: "string",
|
|
891
|
+
description: "Privacy choices URL (HTTPS only)"
|
|
892
|
+
},
|
|
893
|
+
privacyPolicyText: {
|
|
894
|
+
type: "string",
|
|
895
|
+
description: "Privacy policy text (for apps without a URL)"
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
required: ["localizationId"]
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
];
|
|
902
|
+
|
|
903
|
+
// src/tools/apps.tools.ts
|
|
904
|
+
async function listApps(client, input) {
|
|
905
|
+
try {
|
|
906
|
+
const params = validateInput(listAppsInputSchema, input);
|
|
907
|
+
const response = await client.get("/apps", {
|
|
908
|
+
limit: params.limit,
|
|
909
|
+
"fields[apps]": "name,bundleId,sku,primaryLocale"
|
|
910
|
+
});
|
|
911
|
+
return {
|
|
912
|
+
success: true,
|
|
913
|
+
data: response.data.map((app) => ({
|
|
914
|
+
id: app.id,
|
|
915
|
+
name: app.attributes.name,
|
|
916
|
+
bundleId: app.attributes.bundleId,
|
|
917
|
+
sku: app.attributes.sku,
|
|
918
|
+
primaryLocale: app.attributes.primaryLocale
|
|
919
|
+
})),
|
|
920
|
+
meta: {
|
|
921
|
+
total: response.meta?.paging?.total,
|
|
922
|
+
returned: response.data.length
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
} catch (error) {
|
|
926
|
+
return formatErrorResponse(error);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
async function getApp(client, input) {
|
|
930
|
+
try {
|
|
931
|
+
const params = validateInput(getAppInputSchema, input);
|
|
932
|
+
const response = await client.get(`/apps/${params.appId}`, {
|
|
933
|
+
"fields[apps]": "name,bundleId,sku,primaryLocale,contentRightsDeclaration,isOrEverWasMadeForKids",
|
|
934
|
+
include: "appInfos,appStoreVersions"
|
|
935
|
+
});
|
|
936
|
+
const app = response.data;
|
|
937
|
+
return {
|
|
938
|
+
success: true,
|
|
939
|
+
data: {
|
|
940
|
+
id: app.id,
|
|
941
|
+
name: app.attributes.name,
|
|
942
|
+
bundleId: app.attributes.bundleId,
|
|
943
|
+
sku: app.attributes.sku,
|
|
944
|
+
primaryLocale: app.attributes.primaryLocale,
|
|
945
|
+
contentRightsDeclaration: app.attributes.contentRightsDeclaration,
|
|
946
|
+
isOrEverWasMadeForKids: app.attributes.isOrEverWasMadeForKids
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
} catch (error) {
|
|
950
|
+
return formatErrorResponse(error);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
var appsToolDefinitions = [
|
|
954
|
+
{
|
|
955
|
+
name: "list_apps",
|
|
956
|
+
description: "List all apps in your App Store Connect account. Returns app IDs, names, bundle IDs, and SKUs.",
|
|
957
|
+
inputSchema: {
|
|
958
|
+
type: "object",
|
|
959
|
+
properties: {
|
|
960
|
+
limit: {
|
|
961
|
+
type: "number",
|
|
962
|
+
description: "Maximum number of apps to return (1-200)",
|
|
963
|
+
minimum: 1,
|
|
964
|
+
maximum: 200
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
name: "get_app",
|
|
971
|
+
description: "Get detailed information about a specific app by its ID.",
|
|
972
|
+
inputSchema: {
|
|
973
|
+
type: "object",
|
|
974
|
+
properties: {
|
|
975
|
+
appId: {
|
|
976
|
+
type: "string",
|
|
977
|
+
description: "The App Store Connect app ID (numeric string)"
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
required: ["appId"]
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
];
|
|
984
|
+
|
|
985
|
+
// src/tools/beta.tools.ts
|
|
986
|
+
async function listBetaGroups(client, input) {
|
|
987
|
+
try {
|
|
988
|
+
const params = validateInput(listBetaGroupsInputSchema, input);
|
|
989
|
+
const response = await client.get(
|
|
990
|
+
`/apps/${params.appId}/betaGroups`,
|
|
991
|
+
{
|
|
992
|
+
limit: params.limit,
|
|
993
|
+
"fields[betaGroups]": "name,createdDate,isInternalGroup,hasAccessToAllBuilds,publicLinkEnabled,publicLink,feedbackEnabled"
|
|
994
|
+
}
|
|
995
|
+
);
|
|
996
|
+
return {
|
|
997
|
+
success: true,
|
|
998
|
+
data: response.data.map((group) => ({
|
|
999
|
+
id: group.id,
|
|
1000
|
+
name: group.attributes.name,
|
|
1001
|
+
createdDate: group.attributes.createdDate,
|
|
1002
|
+
isInternalGroup: group.attributes.isInternalGroup,
|
|
1003
|
+
hasAccessToAllBuilds: group.attributes.hasAccessToAllBuilds,
|
|
1004
|
+
publicLinkEnabled: group.attributes.publicLinkEnabled,
|
|
1005
|
+
publicLink: group.attributes.publicLink,
|
|
1006
|
+
feedbackEnabled: group.attributes.feedbackEnabled
|
|
1007
|
+
})),
|
|
1008
|
+
meta: {
|
|
1009
|
+
total: response.meta?.paging?.total,
|
|
1010
|
+
returned: response.data.length
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
return formatErrorResponse(error);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
async function listBetaTesters(client, input) {
|
|
1018
|
+
try {
|
|
1019
|
+
const params = validateInput(listBetaTestersInputSchema, input);
|
|
1020
|
+
const response = await client.get(
|
|
1021
|
+
`/betaGroups/${params.betaGroupId}/betaTesters`,
|
|
1022
|
+
{
|
|
1023
|
+
limit: params.limit,
|
|
1024
|
+
"fields[betaTesters]": "firstName,lastName,email,inviteType,state"
|
|
1025
|
+
}
|
|
1026
|
+
);
|
|
1027
|
+
return {
|
|
1028
|
+
success: true,
|
|
1029
|
+
data: response.data.map((tester) => ({
|
|
1030
|
+
id: tester.id,
|
|
1031
|
+
firstName: tester.attributes.firstName,
|
|
1032
|
+
lastName: tester.attributes.lastName,
|
|
1033
|
+
email: tester.attributes.email,
|
|
1034
|
+
inviteType: tester.attributes.inviteType,
|
|
1035
|
+
state: tester.attributes.state
|
|
1036
|
+
})),
|
|
1037
|
+
meta: {
|
|
1038
|
+
total: response.meta?.paging?.total,
|
|
1039
|
+
returned: response.data.length
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
return formatErrorResponse(error);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
async function addBetaTester(client, input) {
|
|
1047
|
+
try {
|
|
1048
|
+
const params = validateInput(addBetaTesterInputSchema, input);
|
|
1049
|
+
const requestBody = {
|
|
1050
|
+
data: {
|
|
1051
|
+
type: "betaTesters",
|
|
1052
|
+
attributes: {
|
|
1053
|
+
email: params.email,
|
|
1054
|
+
firstName: params.firstName,
|
|
1055
|
+
lastName: params.lastName
|
|
1056
|
+
},
|
|
1057
|
+
relationships: {
|
|
1058
|
+
betaGroups: {
|
|
1059
|
+
data: [
|
|
1060
|
+
{
|
|
1061
|
+
type: "betaGroups",
|
|
1062
|
+
id: params.betaGroupId
|
|
1063
|
+
}
|
|
1064
|
+
]
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
const response = await client.post("/betaTesters", requestBody);
|
|
1070
|
+
const tester = response.data;
|
|
1071
|
+
return {
|
|
1072
|
+
success: true,
|
|
1073
|
+
data: {
|
|
1074
|
+
id: tester.id,
|
|
1075
|
+
firstName: tester.attributes.firstName,
|
|
1076
|
+
lastName: tester.attributes.lastName,
|
|
1077
|
+
email: tester.attributes.email,
|
|
1078
|
+
inviteType: tester.attributes.inviteType,
|
|
1079
|
+
state: tester.attributes.state
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
return formatErrorResponse(error);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
async function removeBetaTester(client, input) {
|
|
1087
|
+
try {
|
|
1088
|
+
const params = validateInput(removeBetaTesterInputSchema, input);
|
|
1089
|
+
const requestBody = {
|
|
1090
|
+
data: [
|
|
1091
|
+
{
|
|
1092
|
+
type: "betaTesters",
|
|
1093
|
+
id: params.betaTesterId
|
|
1094
|
+
}
|
|
1095
|
+
]
|
|
1096
|
+
};
|
|
1097
|
+
await client.delete(`/betaGroups/${params.betaGroupId}/relationships/betaTesters`, requestBody);
|
|
1098
|
+
return {
|
|
1099
|
+
success: true,
|
|
1100
|
+
data: {
|
|
1101
|
+
removed: true,
|
|
1102
|
+
betaGroupId: params.betaGroupId,
|
|
1103
|
+
betaTesterId: params.betaTesterId
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
return formatErrorResponse(error);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
var betaToolDefinitions = [
|
|
1111
|
+
{
|
|
1112
|
+
name: "list_beta_groups",
|
|
1113
|
+
description: "List all beta groups for an app. Returns group names, public link info, and settings.",
|
|
1114
|
+
inputSchema: {
|
|
1115
|
+
type: "object",
|
|
1116
|
+
properties: {
|
|
1117
|
+
appId: {
|
|
1118
|
+
type: "string",
|
|
1119
|
+
description: "The App Store Connect app ID"
|
|
1120
|
+
},
|
|
1121
|
+
limit: {
|
|
1122
|
+
type: "number",
|
|
1123
|
+
description: "Maximum number of groups to return (1-200)",
|
|
1124
|
+
minimum: 1,
|
|
1125
|
+
maximum: 200
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
required: ["appId"]
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
name: "list_beta_testers",
|
|
1133
|
+
description: "List all beta testers in a specific beta group.",
|
|
1134
|
+
inputSchema: {
|
|
1135
|
+
type: "object",
|
|
1136
|
+
properties: {
|
|
1137
|
+
betaGroupId: {
|
|
1138
|
+
type: "string",
|
|
1139
|
+
description: "The beta group ID"
|
|
1140
|
+
},
|
|
1141
|
+
limit: {
|
|
1142
|
+
type: "number",
|
|
1143
|
+
description: "Maximum number of testers to return (1-200)",
|
|
1144
|
+
minimum: 1,
|
|
1145
|
+
maximum: 200
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
required: ["betaGroupId"]
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
name: "add_beta_tester",
|
|
1153
|
+
description: "Add a new beta tester to a beta group by email address.",
|
|
1154
|
+
inputSchema: {
|
|
1155
|
+
type: "object",
|
|
1156
|
+
properties: {
|
|
1157
|
+
betaGroupId: {
|
|
1158
|
+
type: "string",
|
|
1159
|
+
description: "The beta group ID to add the tester to"
|
|
1160
|
+
},
|
|
1161
|
+
email: {
|
|
1162
|
+
type: "string",
|
|
1163
|
+
description: "Email address of the beta tester"
|
|
1164
|
+
},
|
|
1165
|
+
firstName: {
|
|
1166
|
+
type: "string",
|
|
1167
|
+
description: "First name of the beta tester (optional)"
|
|
1168
|
+
},
|
|
1169
|
+
lastName: {
|
|
1170
|
+
type: "string",
|
|
1171
|
+
description: "Last name of the beta tester (optional)"
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
required: ["betaGroupId", "email"]
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
name: "remove_beta_tester",
|
|
1179
|
+
description: "Remove a beta tester from a beta group.",
|
|
1180
|
+
inputSchema: {
|
|
1181
|
+
type: "object",
|
|
1182
|
+
properties: {
|
|
1183
|
+
betaGroupId: {
|
|
1184
|
+
type: "string",
|
|
1185
|
+
description: "The beta group ID to remove the tester from"
|
|
1186
|
+
},
|
|
1187
|
+
betaTesterId: {
|
|
1188
|
+
type: "string",
|
|
1189
|
+
description: "The beta tester ID to remove"
|
|
1190
|
+
}
|
|
1191
|
+
},
|
|
1192
|
+
required: ["betaGroupId", "betaTesterId"]
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
];
|
|
1196
|
+
|
|
1197
|
+
// src/tools/builds.tools.ts
|
|
1198
|
+
async function listBuilds(client, input) {
|
|
1199
|
+
try {
|
|
1200
|
+
const params = validateInput(listBuildsInputSchema, input);
|
|
1201
|
+
const response = await client.get(`/apps/${params.appId}/builds`, {
|
|
1202
|
+
limit: params.limit,
|
|
1203
|
+
"fields[builds]": "version,uploadedDate,expirationDate,expired,minOsVersion,processingState,buildAudienceType,usesNonExemptEncryption",
|
|
1204
|
+
sort: "-uploadedDate"
|
|
1205
|
+
});
|
|
1206
|
+
return {
|
|
1207
|
+
success: true,
|
|
1208
|
+
data: response.data.map((build) => ({
|
|
1209
|
+
id: build.id,
|
|
1210
|
+
version: build.attributes.version,
|
|
1211
|
+
uploadedDate: build.attributes.uploadedDate,
|
|
1212
|
+
expirationDate: build.attributes.expirationDate,
|
|
1213
|
+
expired: build.attributes.expired,
|
|
1214
|
+
minOsVersion: build.attributes.minOsVersion,
|
|
1215
|
+
processingState: build.attributes.processingState,
|
|
1216
|
+
buildAudienceType: build.attributes.buildAudienceType,
|
|
1217
|
+
usesNonExemptEncryption: build.attributes.usesNonExemptEncryption
|
|
1218
|
+
})),
|
|
1219
|
+
meta: {
|
|
1220
|
+
total: response.meta?.paging?.total,
|
|
1221
|
+
returned: response.data.length
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
return formatErrorResponse(error);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async function getBuild(client, input) {
|
|
1229
|
+
try {
|
|
1230
|
+
const params = validateInput(getBuildInputSchema, input);
|
|
1231
|
+
const response = await client.get(`/builds/${params.buildId}`, {
|
|
1232
|
+
"fields[builds]": "version,uploadedDate,expirationDate,expired,minOsVersion,processingState,buildAudienceType,usesNonExemptEncryption"
|
|
1233
|
+
});
|
|
1234
|
+
const build = response.data;
|
|
1235
|
+
return {
|
|
1236
|
+
success: true,
|
|
1237
|
+
data: {
|
|
1238
|
+
id: build.id,
|
|
1239
|
+
version: build.attributes.version,
|
|
1240
|
+
uploadedDate: build.attributes.uploadedDate,
|
|
1241
|
+
expirationDate: build.attributes.expirationDate,
|
|
1242
|
+
expired: build.attributes.expired,
|
|
1243
|
+
minOsVersion: build.attributes.minOsVersion,
|
|
1244
|
+
processingState: build.attributes.processingState,
|
|
1245
|
+
buildAudienceType: build.attributes.buildAudienceType,
|
|
1246
|
+
usesNonExemptEncryption: build.attributes.usesNonExemptEncryption
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
return formatErrorResponse(error);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
async function listBetaTesterInvitations(client, input) {
|
|
1254
|
+
try {
|
|
1255
|
+
const params = validateInput(listBetaTesterInvitationsInputSchema, input);
|
|
1256
|
+
const response = await client.get(
|
|
1257
|
+
`/betaGroups/${params.betaGroupId}/betaTesterInvitations`,
|
|
1258
|
+
{
|
|
1259
|
+
limit: params.limit
|
|
1260
|
+
}
|
|
1261
|
+
);
|
|
1262
|
+
return {
|
|
1263
|
+
success: true,
|
|
1264
|
+
data: response.data.map((invitation) => ({
|
|
1265
|
+
id: invitation.id
|
|
1266
|
+
})),
|
|
1267
|
+
meta: {
|
|
1268
|
+
total: response.meta?.paging?.total,
|
|
1269
|
+
returned: response.data.length
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
return formatErrorResponse(error);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
var buildsToolDefinitions = [
|
|
1277
|
+
{
|
|
1278
|
+
name: "list_builds",
|
|
1279
|
+
description: "List all builds for an app, sorted by upload date (newest first).",
|
|
1280
|
+
inputSchema: {
|
|
1281
|
+
type: "object",
|
|
1282
|
+
properties: {
|
|
1283
|
+
appId: {
|
|
1284
|
+
type: "string",
|
|
1285
|
+
description: "The App Store Connect app ID"
|
|
1286
|
+
},
|
|
1287
|
+
limit: {
|
|
1288
|
+
type: "number",
|
|
1289
|
+
description: "Maximum number of builds to return (1-200)",
|
|
1290
|
+
minimum: 1,
|
|
1291
|
+
maximum: 200
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
required: ["appId"]
|
|
1295
|
+
}
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
name: "get_build",
|
|
1299
|
+
description: "Get details of a specific build.",
|
|
1300
|
+
inputSchema: {
|
|
1301
|
+
type: "object",
|
|
1302
|
+
properties: {
|
|
1303
|
+
buildId: {
|
|
1304
|
+
type: "string",
|
|
1305
|
+
description: "The build resource ID"
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
required: ["buildId"]
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
name: "list_beta_tester_invitations",
|
|
1313
|
+
description: "List pending beta tester invitations for a beta group.",
|
|
1314
|
+
inputSchema: {
|
|
1315
|
+
type: "object",
|
|
1316
|
+
properties: {
|
|
1317
|
+
betaGroupId: {
|
|
1318
|
+
type: "string",
|
|
1319
|
+
description: "The beta group ID"
|
|
1320
|
+
},
|
|
1321
|
+
limit: {
|
|
1322
|
+
type: "number",
|
|
1323
|
+
description: "Maximum number of invitations to return (1-200)",
|
|
1324
|
+
minimum: 1,
|
|
1325
|
+
maximum: 200
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
required: ["betaGroupId"]
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
];
|
|
1332
|
+
|
|
1333
|
+
// src/tools/bundle-ids.tools.ts
|
|
1334
|
+
async function listBundleIds(client, input) {
|
|
1335
|
+
try {
|
|
1336
|
+
const params = validateInput(listBundleIdsInputSchema, input);
|
|
1337
|
+
const queryParams = {
|
|
1338
|
+
limit: params.limit,
|
|
1339
|
+
"fields[bundleIds]": "name,identifier,platform,seedId"
|
|
1340
|
+
};
|
|
1341
|
+
if (params.platform) {
|
|
1342
|
+
queryParams["filter[platform]"] = params.platform;
|
|
1343
|
+
}
|
|
1344
|
+
const response = await client.get("/bundleIds", queryParams);
|
|
1345
|
+
return {
|
|
1346
|
+
success: true,
|
|
1347
|
+
data: response.data.map((bundleId) => ({
|
|
1348
|
+
id: bundleId.id,
|
|
1349
|
+
name: bundleId.attributes.name,
|
|
1350
|
+
identifier: bundleId.attributes.identifier,
|
|
1351
|
+
platform: bundleId.attributes.platform,
|
|
1352
|
+
seedId: bundleId.attributes.seedId
|
|
1353
|
+
})),
|
|
1354
|
+
meta: {
|
|
1355
|
+
total: response.meta?.paging?.total,
|
|
1356
|
+
returned: response.data.length
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
return formatErrorResponse(error);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
async function getBundleId(client, input) {
|
|
1364
|
+
try {
|
|
1365
|
+
const params = validateInput(getBundleIdInputSchema, input);
|
|
1366
|
+
const response = await client.get(`/bundleIds/${params.bundleIdId}`, {
|
|
1367
|
+
"fields[bundleIds]": "name,identifier,platform,seedId"
|
|
1368
|
+
});
|
|
1369
|
+
const bundleId = response.data;
|
|
1370
|
+
return {
|
|
1371
|
+
success: true,
|
|
1372
|
+
data: {
|
|
1373
|
+
id: bundleId.id,
|
|
1374
|
+
name: bundleId.attributes.name,
|
|
1375
|
+
identifier: bundleId.attributes.identifier,
|
|
1376
|
+
platform: bundleId.attributes.platform,
|
|
1377
|
+
seedId: bundleId.attributes.seedId
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
return formatErrorResponse(error);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
async function createBundleId(client, input) {
|
|
1385
|
+
try {
|
|
1386
|
+
const params = validateInput(createBundleIdInputSchema, input);
|
|
1387
|
+
const requestBody = {
|
|
1388
|
+
data: {
|
|
1389
|
+
type: "bundleIds",
|
|
1390
|
+
attributes: {
|
|
1391
|
+
identifier: params.identifier,
|
|
1392
|
+
name: params.name,
|
|
1393
|
+
platform: params.platform
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
const response = await client.post("/bundleIds", requestBody);
|
|
1398
|
+
const bundleId = response.data;
|
|
1399
|
+
return {
|
|
1400
|
+
success: true,
|
|
1401
|
+
data: {
|
|
1402
|
+
id: bundleId.id,
|
|
1403
|
+
name: bundleId.attributes.name,
|
|
1404
|
+
identifier: bundleId.attributes.identifier,
|
|
1405
|
+
platform: bundleId.attributes.platform,
|
|
1406
|
+
seedId: bundleId.attributes.seedId
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
return formatErrorResponse(error);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
async function updateBundleId(client, input) {
|
|
1414
|
+
try {
|
|
1415
|
+
const params = validateInput(updateBundleIdInputSchema, input);
|
|
1416
|
+
const requestBody = {
|
|
1417
|
+
data: {
|
|
1418
|
+
type: "bundleIds",
|
|
1419
|
+
id: params.bundleIdId,
|
|
1420
|
+
attributes: {
|
|
1421
|
+
name: params.name
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
const response = await client.patch(
|
|
1426
|
+
`/bundleIds/${params.bundleIdId}`,
|
|
1427
|
+
requestBody
|
|
1428
|
+
);
|
|
1429
|
+
const bundleId = response.data;
|
|
1430
|
+
return {
|
|
1431
|
+
success: true,
|
|
1432
|
+
data: {
|
|
1433
|
+
id: bundleId.id,
|
|
1434
|
+
name: bundleId.attributes.name,
|
|
1435
|
+
identifier: bundleId.attributes.identifier,
|
|
1436
|
+
platform: bundleId.attributes.platform,
|
|
1437
|
+
seedId: bundleId.attributes.seedId
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
return formatErrorResponse(error);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
async function deleteBundleId(client, input) {
|
|
1445
|
+
try {
|
|
1446
|
+
const params = validateInput(deleteBundleIdInputSchema, input);
|
|
1447
|
+
await client.delete(`/bundleIds/${params.bundleIdId}`);
|
|
1448
|
+
return {
|
|
1449
|
+
success: true,
|
|
1450
|
+
data: {
|
|
1451
|
+
deleted: true,
|
|
1452
|
+
bundleIdId: params.bundleIdId
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
return formatErrorResponse(error);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
var bundleIdsToolDefinitions = [
|
|
1460
|
+
{
|
|
1461
|
+
name: "list_bundle_ids",
|
|
1462
|
+
description: "List all bundle IDs registered in App Store Connect. Can filter by platform.",
|
|
1463
|
+
inputSchema: {
|
|
1464
|
+
type: "object",
|
|
1465
|
+
properties: {
|
|
1466
|
+
limit: {
|
|
1467
|
+
type: "number",
|
|
1468
|
+
description: "Maximum number of bundle IDs to return (1-200)",
|
|
1469
|
+
minimum: 1,
|
|
1470
|
+
maximum: 200
|
|
1471
|
+
},
|
|
1472
|
+
platform: {
|
|
1473
|
+
type: "string",
|
|
1474
|
+
enum: ["IOS", "MAC_OS", "TV_OS", "VISION_OS"],
|
|
1475
|
+
description: "Filter by platform"
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
required: []
|
|
1479
|
+
}
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
name: "get_bundle_id",
|
|
1483
|
+
description: "Get details of a specific bundle ID.",
|
|
1484
|
+
inputSchema: {
|
|
1485
|
+
type: "object",
|
|
1486
|
+
properties: {
|
|
1487
|
+
bundleIdId: {
|
|
1488
|
+
type: "string",
|
|
1489
|
+
description: "The bundle ID resource ID"
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
required: ["bundleIdId"]
|
|
1493
|
+
}
|
|
1494
|
+
},
|
|
1495
|
+
{
|
|
1496
|
+
name: "create_bundle_id",
|
|
1497
|
+
description: "Register a new bundle ID in App Store Connect. The identifier must be unique and follow reverse-domain notation (e.g., com.example.app).",
|
|
1498
|
+
inputSchema: {
|
|
1499
|
+
type: "object",
|
|
1500
|
+
properties: {
|
|
1501
|
+
identifier: {
|
|
1502
|
+
type: "string",
|
|
1503
|
+
description: "The bundle identifier (e.g., com.example.app)"
|
|
1504
|
+
},
|
|
1505
|
+
name: {
|
|
1506
|
+
type: "string",
|
|
1507
|
+
description: "A name for the bundle ID"
|
|
1508
|
+
},
|
|
1509
|
+
platform: {
|
|
1510
|
+
type: "string",
|
|
1511
|
+
enum: ["IOS", "MAC_OS", "TV_OS", "VISION_OS"],
|
|
1512
|
+
description: "The platform for this bundle ID"
|
|
1513
|
+
}
|
|
1514
|
+
},
|
|
1515
|
+
required: ["identifier", "name", "platform"]
|
|
1516
|
+
}
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
name: "update_bundle_id",
|
|
1520
|
+
description: "Update a bundle ID's name. Note: The identifier cannot be changed.",
|
|
1521
|
+
inputSchema: {
|
|
1522
|
+
type: "object",
|
|
1523
|
+
properties: {
|
|
1524
|
+
bundleIdId: {
|
|
1525
|
+
type: "string",
|
|
1526
|
+
description: "The bundle ID resource ID"
|
|
1527
|
+
},
|
|
1528
|
+
name: {
|
|
1529
|
+
type: "string",
|
|
1530
|
+
description: "The new name for the bundle ID"
|
|
1531
|
+
}
|
|
1532
|
+
},
|
|
1533
|
+
required: ["bundleIdId", "name"]
|
|
1534
|
+
}
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
name: "delete_bundle_id",
|
|
1538
|
+
description: "Delete a bundle ID. Note: This cannot be undone and may affect apps using this bundle ID.",
|
|
1539
|
+
inputSchema: {
|
|
1540
|
+
type: "object",
|
|
1541
|
+
properties: {
|
|
1542
|
+
bundleIdId: {
|
|
1543
|
+
type: "string",
|
|
1544
|
+
description: "The bundle ID resource ID to delete"
|
|
1545
|
+
}
|
|
1546
|
+
},
|
|
1547
|
+
required: ["bundleIdId"]
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
];
|
|
1551
|
+
|
|
1552
|
+
// src/tools/categories.tools.ts
|
|
1553
|
+
async function listAppCategories(client, input) {
|
|
1554
|
+
try {
|
|
1555
|
+
const params = validateInput(listAppCategoriesInputSchema, input);
|
|
1556
|
+
const queryParams = {
|
|
1557
|
+
limit: params.limit,
|
|
1558
|
+
"fields[appCategories]": "platforms",
|
|
1559
|
+
include: "subcategories"
|
|
1560
|
+
};
|
|
1561
|
+
if (params.platform) {
|
|
1562
|
+
queryParams["filter[platforms]"] = params.platform;
|
|
1563
|
+
}
|
|
1564
|
+
const response = await client.get("/appCategories", queryParams);
|
|
1565
|
+
const included = response.included ?? [];
|
|
1566
|
+
const subcategoriesMap = /* @__PURE__ */ new Map();
|
|
1567
|
+
for (const sub of included) {
|
|
1568
|
+
if (sub.type === "appCategories") {
|
|
1569
|
+
subcategoriesMap.set(sub.id, sub);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return {
|
|
1573
|
+
success: true,
|
|
1574
|
+
data: response.data.map((category) => {
|
|
1575
|
+
const subcategoryData = category.relationships?.subcategories?.data;
|
|
1576
|
+
const subcategoryIds = Array.isArray(subcategoryData) ? subcategoryData.map((s) => s.id) : [];
|
|
1577
|
+
return {
|
|
1578
|
+
id: category.id,
|
|
1579
|
+
platforms: category.attributes.platforms,
|
|
1580
|
+
subcategories: subcategoryIds.map((id) => {
|
|
1581
|
+
const sub = subcategoriesMap.get(id);
|
|
1582
|
+
return sub ? {
|
|
1583
|
+
id: sub.id,
|
|
1584
|
+
platforms: sub.attributes.platforms
|
|
1585
|
+
} : { id };
|
|
1586
|
+
})
|
|
1587
|
+
};
|
|
1588
|
+
}),
|
|
1589
|
+
meta: {
|
|
1590
|
+
total: response.meta?.paging?.total,
|
|
1591
|
+
returned: response.data.length
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
} catch (error) {
|
|
1595
|
+
return formatErrorResponse(error);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
async function getAppPriceSchedule(client, input) {
|
|
1599
|
+
try {
|
|
1600
|
+
const params = validateInput(getAppPriceScheduleInputSchema, input);
|
|
1601
|
+
const response = await client.get(
|
|
1602
|
+
`/apps/${params.appId}/appPriceSchedule`,
|
|
1603
|
+
{
|
|
1604
|
+
include: "baseTerritory,manualPrices,automaticPrices"
|
|
1605
|
+
}
|
|
1606
|
+
);
|
|
1607
|
+
const schedule = response.data;
|
|
1608
|
+
const included = response.included ?? [];
|
|
1609
|
+
let baseTerritory;
|
|
1610
|
+
const baseTerritoryData = schedule.relationships?.baseTerritory?.data;
|
|
1611
|
+
if (baseTerritoryData && !Array.isArray(baseTerritoryData)) {
|
|
1612
|
+
const territory = included.find(
|
|
1613
|
+
(item) => item.type === "territories" && item.id === baseTerritoryData.id
|
|
1614
|
+
);
|
|
1615
|
+
if (territory) {
|
|
1616
|
+
baseTerritory = {
|
|
1617
|
+
id: territory.id,
|
|
1618
|
+
currency: territory.attributes.currency
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return {
|
|
1623
|
+
success: true,
|
|
1624
|
+
data: {
|
|
1625
|
+
id: schedule.id,
|
|
1626
|
+
baseTerritory,
|
|
1627
|
+
hasManualPrices: schedule.relationships?.manualPrices?.data !== void 0 && (Array.isArray(schedule.relationships.manualPrices.data) ? schedule.relationships.manualPrices.data.length > 0 : true),
|
|
1628
|
+
hasAutomaticPrices: schedule.relationships?.automaticPrices?.data !== void 0 && (Array.isArray(schedule.relationships.automaticPrices.data) ? schedule.relationships.automaticPrices.data.length > 0 : true)
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
return formatErrorResponse(error);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
async function getAppAvailability(client, input) {
|
|
1636
|
+
try {
|
|
1637
|
+
const params = validateInput(getAppAvailabilityInputSchema, input);
|
|
1638
|
+
const response = await client.get(
|
|
1639
|
+
`/apps/${params.appId}/appAvailability`,
|
|
1640
|
+
{
|
|
1641
|
+
include: "availableTerritories",
|
|
1642
|
+
"fields[territories]": "currency"
|
|
1643
|
+
}
|
|
1644
|
+
);
|
|
1645
|
+
const availability = response.data;
|
|
1646
|
+
const included = response.included ?? [];
|
|
1647
|
+
const territories = included.filter((item) => item.type === "territories").map((territory) => ({
|
|
1648
|
+
id: territory.id,
|
|
1649
|
+
currency: territory.attributes.currency
|
|
1650
|
+
}));
|
|
1651
|
+
return {
|
|
1652
|
+
success: true,
|
|
1653
|
+
data: {
|
|
1654
|
+
id: availability.id,
|
|
1655
|
+
availableInNewTerritories: availability.attributes.availableInNewTerritories,
|
|
1656
|
+
territories,
|
|
1657
|
+
territoryCount: territories.length
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
return formatErrorResponse(error);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
var categoriesToolDefinitions = [
|
|
1665
|
+
{
|
|
1666
|
+
name: "list_app_categories",
|
|
1667
|
+
description: "List all app categories available in the App Store. Can filter by platform.",
|
|
1668
|
+
inputSchema: {
|
|
1669
|
+
type: "object",
|
|
1670
|
+
properties: {
|
|
1671
|
+
limit: {
|
|
1672
|
+
type: "number",
|
|
1673
|
+
description: "Maximum number of categories to return (1-200)",
|
|
1674
|
+
minimum: 1,
|
|
1675
|
+
maximum: 200
|
|
1676
|
+
},
|
|
1677
|
+
platform: {
|
|
1678
|
+
type: "string",
|
|
1679
|
+
enum: ["IOS", "MAC_OS", "TV_OS", "VISION_OS"],
|
|
1680
|
+
description: "Filter by platform"
|
|
1681
|
+
}
|
|
1682
|
+
},
|
|
1683
|
+
required: []
|
|
1684
|
+
}
|
|
1685
|
+
},
|
|
1686
|
+
{
|
|
1687
|
+
name: "get_app_price_schedule",
|
|
1688
|
+
description: "Get the price schedule for an app, including pricing information.",
|
|
1689
|
+
inputSchema: {
|
|
1690
|
+
type: "object",
|
|
1691
|
+
properties: {
|
|
1692
|
+
appId: {
|
|
1693
|
+
type: "string",
|
|
1694
|
+
description: "The App Store Connect app ID"
|
|
1695
|
+
}
|
|
1696
|
+
},
|
|
1697
|
+
required: ["appId"]
|
|
1698
|
+
}
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
name: "get_app_availability",
|
|
1702
|
+
description: "Get app availability information, including which territories the app is available in.",
|
|
1703
|
+
inputSchema: {
|
|
1704
|
+
type: "object",
|
|
1705
|
+
properties: {
|
|
1706
|
+
appId: {
|
|
1707
|
+
type: "string",
|
|
1708
|
+
description: "The App Store Connect app ID"
|
|
1709
|
+
}
|
|
1710
|
+
},
|
|
1711
|
+
required: ["appId"]
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
];
|
|
1715
|
+
|
|
1716
|
+
// src/tools/devices.tools.ts
|
|
1717
|
+
async function listDevices(client, input) {
|
|
1718
|
+
try {
|
|
1719
|
+
const params = validateInput(listDevicesInputSchema, input);
|
|
1720
|
+
const queryParams = {
|
|
1721
|
+
limit: params.limit,
|
|
1722
|
+
"fields[devices]": "name,platform,udid,deviceClass,status,model,addedDate"
|
|
1723
|
+
};
|
|
1724
|
+
if (params.platform) {
|
|
1725
|
+
queryParams["filter[platform]"] = params.platform;
|
|
1726
|
+
}
|
|
1727
|
+
if (params.status) {
|
|
1728
|
+
queryParams["filter[status]"] = params.status;
|
|
1729
|
+
}
|
|
1730
|
+
const response = await client.get("/devices", queryParams);
|
|
1731
|
+
return {
|
|
1732
|
+
success: true,
|
|
1733
|
+
data: response.data.map((device) => ({
|
|
1734
|
+
id: device.id,
|
|
1735
|
+
name: device.attributes.name,
|
|
1736
|
+
platform: device.attributes.platform,
|
|
1737
|
+
udid: device.attributes.udid,
|
|
1738
|
+
deviceClass: device.attributes.deviceClass,
|
|
1739
|
+
status: device.attributes.status,
|
|
1740
|
+
model: device.attributes.model,
|
|
1741
|
+
addedDate: device.attributes.addedDate
|
|
1742
|
+
})),
|
|
1743
|
+
meta: {
|
|
1744
|
+
total: response.meta?.paging?.total,
|
|
1745
|
+
returned: response.data.length
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
return formatErrorResponse(error);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
async function getDevice(client, input) {
|
|
1753
|
+
try {
|
|
1754
|
+
const params = validateInput(getDeviceInputSchema, input);
|
|
1755
|
+
const response = await client.get(`/devices/${params.deviceId}`, {
|
|
1756
|
+
"fields[devices]": "name,platform,udid,deviceClass,status,model,addedDate"
|
|
1757
|
+
});
|
|
1758
|
+
const device = response.data;
|
|
1759
|
+
return {
|
|
1760
|
+
success: true,
|
|
1761
|
+
data: {
|
|
1762
|
+
id: device.id,
|
|
1763
|
+
name: device.attributes.name,
|
|
1764
|
+
platform: device.attributes.platform,
|
|
1765
|
+
udid: device.attributes.udid,
|
|
1766
|
+
deviceClass: device.attributes.deviceClass,
|
|
1767
|
+
status: device.attributes.status,
|
|
1768
|
+
model: device.attributes.model,
|
|
1769
|
+
addedDate: device.attributes.addedDate
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
return formatErrorResponse(error);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
var devicesToolDefinitions = [
|
|
1777
|
+
{
|
|
1778
|
+
name: "list_devices",
|
|
1779
|
+
description: "List all registered devices in App Store Connect. Can filter by platform and status.",
|
|
1780
|
+
inputSchema: {
|
|
1781
|
+
type: "object",
|
|
1782
|
+
properties: {
|
|
1783
|
+
limit: {
|
|
1784
|
+
type: "number",
|
|
1785
|
+
description: "Maximum number of devices to return (1-200)",
|
|
1786
|
+
minimum: 1,
|
|
1787
|
+
maximum: 200
|
|
1788
|
+
},
|
|
1789
|
+
platform: {
|
|
1790
|
+
type: "string",
|
|
1791
|
+
enum: ["IOS", "MAC_OS", "TV_OS", "VISION_OS"],
|
|
1792
|
+
description: "Filter by platform"
|
|
1793
|
+
},
|
|
1794
|
+
status: {
|
|
1795
|
+
type: "string",
|
|
1796
|
+
enum: ["ENABLED", "DISABLED"],
|
|
1797
|
+
description: "Filter by device status"
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
required: []
|
|
1801
|
+
}
|
|
1802
|
+
},
|
|
1803
|
+
{
|
|
1804
|
+
name: "get_device",
|
|
1805
|
+
description: "Get details of a specific registered device.",
|
|
1806
|
+
inputSchema: {
|
|
1807
|
+
type: "object",
|
|
1808
|
+
properties: {
|
|
1809
|
+
deviceId: {
|
|
1810
|
+
type: "string",
|
|
1811
|
+
description: "The device resource ID"
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
required: ["deviceId"]
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
];
|
|
1818
|
+
|
|
1819
|
+
// src/tools/localizations.tools.ts
|
|
1820
|
+
async function listVersionLocalizations(client, input) {
|
|
1821
|
+
try {
|
|
1822
|
+
const params = validateInput(listVersionLocalizationsInputSchema, input);
|
|
1823
|
+
const response = await client.get(
|
|
1824
|
+
`/appStoreVersions/${params.versionId}/appStoreVersionLocalizations`,
|
|
1825
|
+
{
|
|
1826
|
+
limit: params.limit,
|
|
1827
|
+
"fields[appStoreVersionLocalizations]": "locale,description,keywords,marketingUrl,promotionalText,supportUrl,whatsNew"
|
|
1828
|
+
}
|
|
1829
|
+
);
|
|
1830
|
+
return {
|
|
1831
|
+
success: true,
|
|
1832
|
+
data: response.data.map((loc) => ({
|
|
1833
|
+
id: loc.id,
|
|
1834
|
+
locale: loc.attributes.locale,
|
|
1835
|
+
description: loc.attributes.description,
|
|
1836
|
+
keywords: loc.attributes.keywords,
|
|
1837
|
+
marketingUrl: loc.attributes.marketingUrl,
|
|
1838
|
+
promotionalText: loc.attributes.promotionalText,
|
|
1839
|
+
supportUrl: loc.attributes.supportUrl,
|
|
1840
|
+
whatsNew: loc.attributes.whatsNew
|
|
1841
|
+
})),
|
|
1842
|
+
meta: {
|
|
1843
|
+
total: response.meta?.paging?.total,
|
|
1844
|
+
returned: response.data.length
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
} catch (error) {
|
|
1848
|
+
return formatErrorResponse(error);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
async function getVersionLocalization(client, input) {
|
|
1852
|
+
try {
|
|
1853
|
+
const params = validateInput(getVersionLocalizationInputSchema, input);
|
|
1854
|
+
const response = await client.get(
|
|
1855
|
+
`/appStoreVersionLocalizations/${params.localizationId}`,
|
|
1856
|
+
{
|
|
1857
|
+
"fields[appStoreVersionLocalizations]": "locale,description,keywords,marketingUrl,promotionalText,supportUrl,whatsNew"
|
|
1858
|
+
}
|
|
1859
|
+
);
|
|
1860
|
+
const loc = response.data;
|
|
1861
|
+
return {
|
|
1862
|
+
success: true,
|
|
1863
|
+
data: {
|
|
1864
|
+
id: loc.id,
|
|
1865
|
+
locale: loc.attributes.locale,
|
|
1866
|
+
description: loc.attributes.description,
|
|
1867
|
+
keywords: loc.attributes.keywords,
|
|
1868
|
+
marketingUrl: loc.attributes.marketingUrl,
|
|
1869
|
+
promotionalText: loc.attributes.promotionalText,
|
|
1870
|
+
supportUrl: loc.attributes.supportUrl,
|
|
1871
|
+
whatsNew: loc.attributes.whatsNew
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
return formatErrorResponse(error);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
async function createVersionLocalization(client, input) {
|
|
1879
|
+
try {
|
|
1880
|
+
const params = validateInput(createVersionLocalizationInputSchema, input);
|
|
1881
|
+
const requestBody = {
|
|
1882
|
+
data: {
|
|
1883
|
+
type: "appStoreVersionLocalizations",
|
|
1884
|
+
attributes: {
|
|
1885
|
+
locale: params.locale,
|
|
1886
|
+
description: params.description,
|
|
1887
|
+
keywords: params.keywords,
|
|
1888
|
+
marketingUrl: params.marketingUrl,
|
|
1889
|
+
promotionalText: params.promotionalText,
|
|
1890
|
+
supportUrl: params.supportUrl,
|
|
1891
|
+
whatsNew: params.whatsNew
|
|
1892
|
+
},
|
|
1893
|
+
relationships: {
|
|
1894
|
+
appStoreVersion: {
|
|
1895
|
+
data: {
|
|
1896
|
+
type: "appStoreVersions",
|
|
1897
|
+
id: params.versionId
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
};
|
|
1903
|
+
const response = await client.post(
|
|
1904
|
+
"/appStoreVersionLocalizations",
|
|
1905
|
+
requestBody
|
|
1906
|
+
);
|
|
1907
|
+
const loc = response.data;
|
|
1908
|
+
return {
|
|
1909
|
+
success: true,
|
|
1910
|
+
data: {
|
|
1911
|
+
id: loc.id,
|
|
1912
|
+
locale: loc.attributes.locale,
|
|
1913
|
+
description: loc.attributes.description,
|
|
1914
|
+
keywords: loc.attributes.keywords,
|
|
1915
|
+
marketingUrl: loc.attributes.marketingUrl,
|
|
1916
|
+
promotionalText: loc.attributes.promotionalText,
|
|
1917
|
+
supportUrl: loc.attributes.supportUrl,
|
|
1918
|
+
whatsNew: loc.attributes.whatsNew
|
|
1919
|
+
}
|
|
1920
|
+
};
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
return formatErrorResponse(error);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
async function updateVersionLocalization(client, input) {
|
|
1926
|
+
try {
|
|
1927
|
+
const params = validateInput(updateVersionLocalizationInputSchema, input);
|
|
1928
|
+
const requestBody = {
|
|
1929
|
+
data: {
|
|
1930
|
+
type: "appStoreVersionLocalizations",
|
|
1931
|
+
id: params.localizationId,
|
|
1932
|
+
attributes: {
|
|
1933
|
+
description: params.description,
|
|
1934
|
+
keywords: params.keywords,
|
|
1935
|
+
marketingUrl: params.marketingUrl,
|
|
1936
|
+
promotionalText: params.promotionalText,
|
|
1937
|
+
supportUrl: params.supportUrl,
|
|
1938
|
+
whatsNew: params.whatsNew
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
const response = await client.patch(
|
|
1943
|
+
`/appStoreVersionLocalizations/${params.localizationId}`,
|
|
1944
|
+
requestBody
|
|
1945
|
+
);
|
|
1946
|
+
const loc = response.data;
|
|
1947
|
+
return {
|
|
1948
|
+
success: true,
|
|
1949
|
+
data: {
|
|
1950
|
+
id: loc.id,
|
|
1951
|
+
locale: loc.attributes.locale,
|
|
1952
|
+
description: loc.attributes.description,
|
|
1953
|
+
keywords: loc.attributes.keywords,
|
|
1954
|
+
marketingUrl: loc.attributes.marketingUrl,
|
|
1955
|
+
promotionalText: loc.attributes.promotionalText,
|
|
1956
|
+
supportUrl: loc.attributes.supportUrl,
|
|
1957
|
+
whatsNew: loc.attributes.whatsNew
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
} catch (error) {
|
|
1961
|
+
return formatErrorResponse(error);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
async function deleteVersionLocalization(client, input) {
|
|
1965
|
+
try {
|
|
1966
|
+
const params = validateInput(deleteVersionLocalizationInputSchema, input);
|
|
1967
|
+
await client.delete(`/appStoreVersionLocalizations/${params.localizationId}`);
|
|
1968
|
+
return {
|
|
1969
|
+
success: true,
|
|
1970
|
+
data: {
|
|
1971
|
+
deleted: true,
|
|
1972
|
+
localizationId: params.localizationId
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
} catch (error) {
|
|
1976
|
+
return formatErrorResponse(error);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
var localizationsToolDefinitions = [
|
|
1980
|
+
{
|
|
1981
|
+
name: "list_version_localizations",
|
|
1982
|
+
description: "List all localizations for an App Store version. Returns description, keywords, what's new, and URLs for each locale.",
|
|
1983
|
+
inputSchema: {
|
|
1984
|
+
type: "object",
|
|
1985
|
+
properties: {
|
|
1986
|
+
versionId: {
|
|
1987
|
+
type: "string",
|
|
1988
|
+
description: "The App Store version ID"
|
|
1989
|
+
},
|
|
1990
|
+
limit: {
|
|
1991
|
+
type: "number",
|
|
1992
|
+
description: "Maximum number of localizations to return (1-200)",
|
|
1993
|
+
minimum: 1,
|
|
1994
|
+
maximum: 200
|
|
1995
|
+
}
|
|
1996
|
+
},
|
|
1997
|
+
required: ["versionId"]
|
|
1998
|
+
}
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
name: "get_version_localization",
|
|
2002
|
+
description: "Get detailed information about a specific version localization.",
|
|
2003
|
+
inputSchema: {
|
|
2004
|
+
type: "object",
|
|
2005
|
+
properties: {
|
|
2006
|
+
localizationId: {
|
|
2007
|
+
type: "string",
|
|
2008
|
+
description: "The localization ID"
|
|
2009
|
+
}
|
|
2010
|
+
},
|
|
2011
|
+
required: ["localizationId"]
|
|
2012
|
+
}
|
|
2013
|
+
},
|
|
2014
|
+
{
|
|
2015
|
+
name: "create_version_localization",
|
|
2016
|
+
description: "Create a new localization for an App Store version. Add descriptions, keywords, and other metadata in a specific locale.",
|
|
2017
|
+
inputSchema: {
|
|
2018
|
+
type: "object",
|
|
2019
|
+
properties: {
|
|
2020
|
+
versionId: {
|
|
2021
|
+
type: "string",
|
|
2022
|
+
description: "The App Store version ID"
|
|
2023
|
+
},
|
|
2024
|
+
locale: {
|
|
2025
|
+
type: "string",
|
|
2026
|
+
description: "Locale code (e.g., 'en-US', 'ja', 'zh-Hans')"
|
|
2027
|
+
},
|
|
2028
|
+
description: {
|
|
2029
|
+
type: "string",
|
|
2030
|
+
description: "App description (max 4000 characters)"
|
|
2031
|
+
},
|
|
2032
|
+
keywords: {
|
|
2033
|
+
type: "string",
|
|
2034
|
+
description: "Keywords for search (max 100 characters, comma-separated)"
|
|
2035
|
+
},
|
|
2036
|
+
whatsNew: {
|
|
2037
|
+
type: "string",
|
|
2038
|
+
description: "What's new in this version (max 4000 characters)"
|
|
2039
|
+
},
|
|
2040
|
+
promotionalText: {
|
|
2041
|
+
type: "string",
|
|
2042
|
+
description: "Promotional text (max 170 characters)"
|
|
2043
|
+
},
|
|
2044
|
+
marketingUrl: {
|
|
2045
|
+
type: "string",
|
|
2046
|
+
description: "Marketing URL (HTTPS only)"
|
|
2047
|
+
},
|
|
2048
|
+
supportUrl: {
|
|
2049
|
+
type: "string",
|
|
2050
|
+
description: "Support URL (HTTPS only)"
|
|
2051
|
+
}
|
|
2052
|
+
},
|
|
2053
|
+
required: ["versionId", "locale"]
|
|
2054
|
+
}
|
|
2055
|
+
},
|
|
2056
|
+
{
|
|
2057
|
+
name: "update_version_localization",
|
|
2058
|
+
description: "Update an existing version localization. Only provided fields will be updated.",
|
|
2059
|
+
inputSchema: {
|
|
2060
|
+
type: "object",
|
|
2061
|
+
properties: {
|
|
2062
|
+
localizationId: {
|
|
2063
|
+
type: "string",
|
|
2064
|
+
description: "The localization ID to update"
|
|
2065
|
+
},
|
|
2066
|
+
description: {
|
|
2067
|
+
type: "string",
|
|
2068
|
+
description: "App description (max 4000 characters)"
|
|
2069
|
+
},
|
|
2070
|
+
keywords: {
|
|
2071
|
+
type: "string",
|
|
2072
|
+
description: "Keywords for search (max 100 characters, comma-separated)"
|
|
2073
|
+
},
|
|
2074
|
+
whatsNew: {
|
|
2075
|
+
type: "string",
|
|
2076
|
+
description: "What's new in this version (max 4000 characters)"
|
|
2077
|
+
},
|
|
2078
|
+
promotionalText: {
|
|
2079
|
+
type: "string",
|
|
2080
|
+
description: "Promotional text (max 170 characters)"
|
|
2081
|
+
},
|
|
2082
|
+
marketingUrl: {
|
|
2083
|
+
type: "string",
|
|
2084
|
+
description: "Marketing URL (HTTPS only)"
|
|
2085
|
+
},
|
|
2086
|
+
supportUrl: {
|
|
2087
|
+
type: "string",
|
|
2088
|
+
description: "Support URL (HTTPS only)"
|
|
2089
|
+
}
|
|
2090
|
+
},
|
|
2091
|
+
required: ["localizationId"]
|
|
2092
|
+
}
|
|
2093
|
+
},
|
|
2094
|
+
{
|
|
2095
|
+
name: "delete_version_localization",
|
|
2096
|
+
description: "Delete a version localization. Cannot delete the primary locale.",
|
|
2097
|
+
inputSchema: {
|
|
2098
|
+
type: "object",
|
|
2099
|
+
properties: {
|
|
2100
|
+
localizationId: {
|
|
2101
|
+
type: "string",
|
|
2102
|
+
description: "The localization ID to delete"
|
|
2103
|
+
}
|
|
2104
|
+
},
|
|
2105
|
+
required: ["localizationId"]
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
];
|
|
2109
|
+
|
|
2110
|
+
// src/tools/screenshots.tools.ts
|
|
2111
|
+
import * as crypto from "crypto";
|
|
2112
|
+
import * as fs2 from "fs/promises";
|
|
2113
|
+
async function listScreenshotSets(client, input) {
|
|
2114
|
+
try {
|
|
2115
|
+
const params = validateInput(listScreenshotSetsInputSchema, input);
|
|
2116
|
+
const response = await client.get(
|
|
2117
|
+
`/appStoreVersionLocalizations/${params.localizationId}/appScreenshotSets`,
|
|
2118
|
+
{
|
|
2119
|
+
limit: params.limit,
|
|
2120
|
+
"fields[appScreenshotSets]": "screenshotDisplayType"
|
|
2121
|
+
}
|
|
2122
|
+
);
|
|
2123
|
+
return {
|
|
2124
|
+
success: true,
|
|
2125
|
+
data: response.data.map((set) => ({
|
|
2126
|
+
id: set.id,
|
|
2127
|
+
screenshotDisplayType: set.attributes.screenshotDisplayType
|
|
2128
|
+
})),
|
|
2129
|
+
meta: {
|
|
2130
|
+
total: response.meta?.paging?.total,
|
|
2131
|
+
returned: response.data.length
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
} catch (error) {
|
|
2135
|
+
return formatErrorResponse(error);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
async function listScreenshots(client, input) {
|
|
2139
|
+
try {
|
|
2140
|
+
const params = validateInput(listScreenshotsInputSchema, input);
|
|
2141
|
+
const response = await client.get(
|
|
2142
|
+
`/appScreenshotSets/${params.screenshotSetId}/appScreenshots`,
|
|
2143
|
+
{
|
|
2144
|
+
limit: params.limit,
|
|
2145
|
+
"fields[appScreenshots]": "fileSize,fileName,sourceFileChecksum,imageAsset,assetDeliveryState"
|
|
2146
|
+
}
|
|
2147
|
+
);
|
|
2148
|
+
return {
|
|
2149
|
+
success: true,
|
|
2150
|
+
data: response.data.map((screenshot) => ({
|
|
2151
|
+
id: screenshot.id,
|
|
2152
|
+
fileName: screenshot.attributes.fileName,
|
|
2153
|
+
fileSize: screenshot.attributes.fileSize,
|
|
2154
|
+
sourceFileChecksum: screenshot.attributes.sourceFileChecksum,
|
|
2155
|
+
imageAsset: screenshot.attributes.imageAsset,
|
|
2156
|
+
assetDeliveryState: screenshot.attributes.assetDeliveryState
|
|
2157
|
+
})),
|
|
2158
|
+
meta: {
|
|
2159
|
+
total: response.meta?.paging?.total,
|
|
2160
|
+
returned: response.data.length
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
} catch (error) {
|
|
2164
|
+
return formatErrorResponse(error);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
async function uploadScreenshot(client, input) {
|
|
2168
|
+
try {
|
|
2169
|
+
const params = validateInput(uploadScreenshotInputSchema, input);
|
|
2170
|
+
let fileBuffer;
|
|
2171
|
+
try {
|
|
2172
|
+
fileBuffer = await fs2.readFile(params.filePath);
|
|
2173
|
+
} catch (_error) {
|
|
2174
|
+
throw new ASCError(`Failed to read file: ${params.filePath}`, "FILE_READ_ERROR", 400);
|
|
2175
|
+
}
|
|
2176
|
+
if (fileBuffer.length !== params.fileSize) {
|
|
2177
|
+
throw new ASCError(
|
|
2178
|
+
`File size mismatch: expected ${params.fileSize}, got ${fileBuffer.length}`,
|
|
2179
|
+
"FILE_SIZE_MISMATCH",
|
|
2180
|
+
400
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
const reserveRequest = {
|
|
2184
|
+
data: {
|
|
2185
|
+
type: "appScreenshots",
|
|
2186
|
+
attributes: {
|
|
2187
|
+
fileName: params.fileName,
|
|
2188
|
+
fileSize: params.fileSize
|
|
2189
|
+
},
|
|
2190
|
+
relationships: {
|
|
2191
|
+
appScreenshotSet: {
|
|
2192
|
+
data: {
|
|
2193
|
+
type: "appScreenshotSets",
|
|
2194
|
+
id: params.screenshotSetId
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
const reserveResponse = await client.post(
|
|
2201
|
+
"/appScreenshots",
|
|
2202
|
+
reserveRequest
|
|
2203
|
+
);
|
|
2204
|
+
const screenshot = reserveResponse.data;
|
|
2205
|
+
const uploadOperations = screenshot.attributes.uploadOperations;
|
|
2206
|
+
if (!uploadOperations?.length) {
|
|
2207
|
+
throw new ASCError("No upload operations provided", "UPLOAD_ERROR", 500);
|
|
2208
|
+
}
|
|
2209
|
+
for (const operation of uploadOperations) {
|
|
2210
|
+
const chunk = fileBuffer.subarray(operation.offset, operation.offset + operation.length);
|
|
2211
|
+
const headers = {};
|
|
2212
|
+
for (const header of operation.requestHeaders) {
|
|
2213
|
+
headers[header.name] = header.value;
|
|
2214
|
+
}
|
|
2215
|
+
const uploadResponse = await client.rawRequest(operation.url, {
|
|
2216
|
+
method: operation.method,
|
|
2217
|
+
headers,
|
|
2218
|
+
body: chunk
|
|
2219
|
+
});
|
|
2220
|
+
if (!uploadResponse.ok) {
|
|
2221
|
+
throw new ASCError(
|
|
2222
|
+
`Chunk upload failed: ${uploadResponse.status}`,
|
|
2223
|
+
"UPLOAD_ERROR",
|
|
2224
|
+
uploadResponse.status
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
const checksum = crypto.createHash("md5").update(fileBuffer).digest("base64");
|
|
2229
|
+
const commitRequest = {
|
|
2230
|
+
data: {
|
|
2231
|
+
type: "appScreenshots",
|
|
2232
|
+
id: screenshot.id,
|
|
2233
|
+
attributes: {
|
|
2234
|
+
sourceFileChecksum: checksum,
|
|
2235
|
+
uploaded: true
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
const commitResponse = await client.patch(
|
|
2240
|
+
`/appScreenshots/${screenshot.id}`,
|
|
2241
|
+
commitRequest
|
|
2242
|
+
);
|
|
2243
|
+
const finalScreenshot = commitResponse.data;
|
|
2244
|
+
return {
|
|
2245
|
+
success: true,
|
|
2246
|
+
data: {
|
|
2247
|
+
id: finalScreenshot.id,
|
|
2248
|
+
fileName: finalScreenshot.attributes.fileName,
|
|
2249
|
+
fileSize: finalScreenshot.attributes.fileSize,
|
|
2250
|
+
sourceFileChecksum: finalScreenshot.attributes.sourceFileChecksum,
|
|
2251
|
+
assetDeliveryState: finalScreenshot.attributes.assetDeliveryState
|
|
2252
|
+
}
|
|
2253
|
+
};
|
|
2254
|
+
} catch (error) {
|
|
2255
|
+
return formatErrorResponse(error);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
var screenshotsToolDefinitions = [
|
|
2259
|
+
{
|
|
2260
|
+
name: "list_screenshot_sets",
|
|
2261
|
+
description: "List all screenshot sets for a version localization. Each set represents a different display type (device size).",
|
|
2262
|
+
inputSchema: {
|
|
2263
|
+
type: "object",
|
|
2264
|
+
properties: {
|
|
2265
|
+
localizationId: {
|
|
2266
|
+
type: "string",
|
|
2267
|
+
description: "The version localization ID"
|
|
2268
|
+
},
|
|
2269
|
+
limit: {
|
|
2270
|
+
type: "number",
|
|
2271
|
+
description: "Maximum number of sets to return (1-200)",
|
|
2272
|
+
minimum: 1,
|
|
2273
|
+
maximum: 200
|
|
2274
|
+
}
|
|
2275
|
+
},
|
|
2276
|
+
required: ["localizationId"]
|
|
2277
|
+
}
|
|
2278
|
+
},
|
|
2279
|
+
{
|
|
2280
|
+
name: "list_screenshots",
|
|
2281
|
+
description: "List all screenshots in a screenshot set.",
|
|
2282
|
+
inputSchema: {
|
|
2283
|
+
type: "object",
|
|
2284
|
+
properties: {
|
|
2285
|
+
screenshotSetId: {
|
|
2286
|
+
type: "string",
|
|
2287
|
+
description: "The screenshot set ID"
|
|
2288
|
+
},
|
|
2289
|
+
limit: {
|
|
2290
|
+
type: "number",
|
|
2291
|
+
description: "Maximum number of screenshots to return (1-200)",
|
|
2292
|
+
minimum: 1,
|
|
2293
|
+
maximum: 200
|
|
2294
|
+
}
|
|
2295
|
+
},
|
|
2296
|
+
required: ["screenshotSetId"]
|
|
2297
|
+
}
|
|
2298
|
+
},
|
|
2299
|
+
{
|
|
2300
|
+
name: "upload_screenshot",
|
|
2301
|
+
description: "Upload a new screenshot to a screenshot set. Provide the local file path, and this tool will handle the multi-step upload process.",
|
|
2302
|
+
inputSchema: {
|
|
2303
|
+
type: "object",
|
|
2304
|
+
properties: {
|
|
2305
|
+
screenshotSetId: {
|
|
2306
|
+
type: "string",
|
|
2307
|
+
description: "The screenshot set ID to upload to"
|
|
2308
|
+
},
|
|
2309
|
+
fileName: {
|
|
2310
|
+
type: "string",
|
|
2311
|
+
description: "Name for the screenshot file"
|
|
2312
|
+
},
|
|
2313
|
+
fileSize: {
|
|
2314
|
+
type: "number",
|
|
2315
|
+
description: "Size of the file in bytes"
|
|
2316
|
+
},
|
|
2317
|
+
filePath: {
|
|
2318
|
+
type: "string",
|
|
2319
|
+
description: "Local path to the screenshot file"
|
|
2320
|
+
}
|
|
2321
|
+
},
|
|
2322
|
+
required: ["screenshotSetId", "fileName", "fileSize", "filePath"]
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
];
|
|
2326
|
+
|
|
2327
|
+
// src/tools/users.tools.ts
|
|
2328
|
+
async function listUsers(client, input) {
|
|
2329
|
+
try {
|
|
2330
|
+
const params = validateInput(listUsersInputSchema, input);
|
|
2331
|
+
const queryParams = {
|
|
2332
|
+
limit: params.limit,
|
|
2333
|
+
"fields[users]": "username,firstName,lastName,email,roles,allAppsVisible,provisioningAllowed"
|
|
2334
|
+
};
|
|
2335
|
+
if (params.roles && params.roles.length > 0) {
|
|
2336
|
+
queryParams["filter[roles]"] = params.roles.join(",");
|
|
2337
|
+
}
|
|
2338
|
+
const response = await client.get("/users", queryParams);
|
|
2339
|
+
return {
|
|
2340
|
+
success: true,
|
|
2341
|
+
data: response.data.map((user) => ({
|
|
2342
|
+
id: user.id,
|
|
2343
|
+
username: user.attributes.username,
|
|
2344
|
+
firstName: user.attributes.firstName,
|
|
2345
|
+
lastName: user.attributes.lastName,
|
|
2346
|
+
email: user.attributes.email,
|
|
2347
|
+
roles: user.attributes.roles,
|
|
2348
|
+
allAppsVisible: user.attributes.allAppsVisible,
|
|
2349
|
+
provisioningAllowed: user.attributes.provisioningAllowed
|
|
2350
|
+
})),
|
|
2351
|
+
meta: {
|
|
2352
|
+
total: response.meta?.paging?.total,
|
|
2353
|
+
returned: response.data.length
|
|
2354
|
+
}
|
|
2355
|
+
};
|
|
2356
|
+
} catch (error) {
|
|
2357
|
+
return formatErrorResponse(error);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
async function getUser(client, input) {
|
|
2361
|
+
try {
|
|
2362
|
+
const params = validateInput(getUserInputSchema, input);
|
|
2363
|
+
const response = await client.get(`/users/${params.userId}`, {
|
|
2364
|
+
"fields[users]": "username,firstName,lastName,email,roles,allAppsVisible,provisioningAllowed"
|
|
2365
|
+
});
|
|
2366
|
+
const user = response.data;
|
|
2367
|
+
return {
|
|
2368
|
+
success: true,
|
|
2369
|
+
data: {
|
|
2370
|
+
id: user.id,
|
|
2371
|
+
username: user.attributes.username,
|
|
2372
|
+
firstName: user.attributes.firstName,
|
|
2373
|
+
lastName: user.attributes.lastName,
|
|
2374
|
+
email: user.attributes.email,
|
|
2375
|
+
roles: user.attributes.roles,
|
|
2376
|
+
allAppsVisible: user.attributes.allAppsVisible,
|
|
2377
|
+
provisioningAllowed: user.attributes.provisioningAllowed
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
} catch (error) {
|
|
2381
|
+
return formatErrorResponse(error);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
var usersToolDefinitions = [
|
|
2385
|
+
{
|
|
2386
|
+
name: "list_users",
|
|
2387
|
+
description: "List all users in your App Store Connect team. Can filter by roles.",
|
|
2388
|
+
inputSchema: {
|
|
2389
|
+
type: "object",
|
|
2390
|
+
properties: {
|
|
2391
|
+
limit: {
|
|
2392
|
+
type: "number",
|
|
2393
|
+
description: "Maximum number of users to return (1-200)",
|
|
2394
|
+
minimum: 1,
|
|
2395
|
+
maximum: 200
|
|
2396
|
+
},
|
|
2397
|
+
roles: {
|
|
2398
|
+
type: "array",
|
|
2399
|
+
items: {
|
|
2400
|
+
type: "string",
|
|
2401
|
+
enum: [
|
|
2402
|
+
"ADMIN",
|
|
2403
|
+
"FINANCE",
|
|
2404
|
+
"TECHNICAL",
|
|
2405
|
+
"SALES",
|
|
2406
|
+
"DEVELOPER",
|
|
2407
|
+
"MARKETING",
|
|
2408
|
+
"APP_MANAGER",
|
|
2409
|
+
"CUSTOMER_SUPPORT",
|
|
2410
|
+
"ACCESS_TO_REPORTS",
|
|
2411
|
+
"READ_ONLY"
|
|
2412
|
+
]
|
|
2413
|
+
},
|
|
2414
|
+
description: "Filter by user roles"
|
|
2415
|
+
}
|
|
2416
|
+
},
|
|
2417
|
+
required: []
|
|
2418
|
+
}
|
|
2419
|
+
},
|
|
2420
|
+
{
|
|
2421
|
+
name: "get_user",
|
|
2422
|
+
description: "Get details of a specific team user.",
|
|
2423
|
+
inputSchema: {
|
|
2424
|
+
type: "object",
|
|
2425
|
+
properties: {
|
|
2426
|
+
userId: {
|
|
2427
|
+
type: "string",
|
|
2428
|
+
description: "The user resource ID"
|
|
2429
|
+
}
|
|
2430
|
+
},
|
|
2431
|
+
required: ["userId"]
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
];
|
|
2435
|
+
|
|
2436
|
+
// src/tools/versions.tools.ts
|
|
2437
|
+
async function listAppVersions(client, input) {
|
|
2438
|
+
try {
|
|
2439
|
+
const params = validateInput(listAppVersionsInputSchema, input);
|
|
2440
|
+
const queryParams = {
|
|
2441
|
+
limit: params.limit,
|
|
2442
|
+
"fields[appStoreVersions]": "platform,versionString,appStoreState,copyright,releaseType,createdDate"
|
|
2443
|
+
};
|
|
2444
|
+
if (params.platform) {
|
|
2445
|
+
queryParams["filter[platform]"] = params.platform;
|
|
2446
|
+
}
|
|
2447
|
+
if (params.versionState) {
|
|
2448
|
+
queryParams["filter[appStoreState]"] = params.versionState;
|
|
2449
|
+
}
|
|
2450
|
+
const response = await client.get(
|
|
2451
|
+
`/apps/${params.appId}/appStoreVersions`,
|
|
2452
|
+
queryParams
|
|
2453
|
+
);
|
|
2454
|
+
return {
|
|
2455
|
+
success: true,
|
|
2456
|
+
data: response.data.map((version) => ({
|
|
2457
|
+
id: version.id,
|
|
2458
|
+
platform: version.attributes.platform,
|
|
2459
|
+
versionString: version.attributes.versionString,
|
|
2460
|
+
state: version.attributes.appStoreState,
|
|
2461
|
+
copyright: version.attributes.copyright,
|
|
2462
|
+
releaseType: version.attributes.releaseType,
|
|
2463
|
+
createdDate: version.attributes.createdDate
|
|
2464
|
+
})),
|
|
2465
|
+
meta: {
|
|
2466
|
+
total: response.meta?.paging?.total,
|
|
2467
|
+
returned: response.data.length
|
|
2468
|
+
}
|
|
2469
|
+
};
|
|
2470
|
+
} catch (error) {
|
|
2471
|
+
return formatErrorResponse(error);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
async function getAppVersion(client, input) {
|
|
2475
|
+
try {
|
|
2476
|
+
const params = validateInput(getAppVersionInputSchema, input);
|
|
2477
|
+
const response = await client.get(
|
|
2478
|
+
`/appStoreVersions/${params.versionId}`,
|
|
2479
|
+
{
|
|
2480
|
+
"fields[appStoreVersions]": "platform,versionString,appStoreState,copyright,releaseType,earliestReleaseDate,usesIdfa,downloadable,createdDate"
|
|
2481
|
+
}
|
|
2482
|
+
);
|
|
2483
|
+
const version = response.data;
|
|
2484
|
+
return {
|
|
2485
|
+
success: true,
|
|
2486
|
+
data: {
|
|
2487
|
+
id: version.id,
|
|
2488
|
+
platform: version.attributes.platform,
|
|
2489
|
+
versionString: version.attributes.versionString,
|
|
2490
|
+
state: version.attributes.appStoreState,
|
|
2491
|
+
copyright: version.attributes.copyright,
|
|
2492
|
+
releaseType: version.attributes.releaseType,
|
|
2493
|
+
earliestReleaseDate: version.attributes.earliestReleaseDate,
|
|
2494
|
+
usesIdfa: version.attributes.usesIdfa,
|
|
2495
|
+
downloadable: version.attributes.downloadable,
|
|
2496
|
+
createdDate: version.attributes.createdDate
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
} catch (error) {
|
|
2500
|
+
return formatErrorResponse(error);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
async function createAppVersion(client, input) {
|
|
2504
|
+
try {
|
|
2505
|
+
const params = validateInput(createAppVersionInputSchema, input);
|
|
2506
|
+
const requestBody = {
|
|
2507
|
+
data: {
|
|
2508
|
+
type: "appStoreVersions",
|
|
2509
|
+
attributes: {
|
|
2510
|
+
platform: params.platform,
|
|
2511
|
+
versionString: params.versionString,
|
|
2512
|
+
copyright: params.copyright,
|
|
2513
|
+
releaseType: params.releaseType,
|
|
2514
|
+
earliestReleaseDate: params.earliestReleaseDate
|
|
2515
|
+
},
|
|
2516
|
+
relationships: {
|
|
2517
|
+
app: {
|
|
2518
|
+
data: {
|
|
2519
|
+
type: "apps",
|
|
2520
|
+
id: params.appId
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
const response = await client.post(
|
|
2527
|
+
"/appStoreVersions",
|
|
2528
|
+
requestBody
|
|
2529
|
+
);
|
|
2530
|
+
const version = response.data;
|
|
2531
|
+
return {
|
|
2532
|
+
success: true,
|
|
2533
|
+
data: {
|
|
2534
|
+
id: version.id,
|
|
2535
|
+
platform: version.attributes.platform,
|
|
2536
|
+
versionString: version.attributes.versionString,
|
|
2537
|
+
state: version.attributes.appStoreState,
|
|
2538
|
+
copyright: version.attributes.copyright,
|
|
2539
|
+
releaseType: version.attributes.releaseType,
|
|
2540
|
+
createdDate: version.attributes.createdDate
|
|
2541
|
+
}
|
|
2542
|
+
};
|
|
2543
|
+
} catch (error) {
|
|
2544
|
+
return formatErrorResponse(error);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
var versionsToolDefinitions = [
|
|
2548
|
+
{
|
|
2549
|
+
name: "list_app_versions",
|
|
2550
|
+
description: "List all App Store versions for an app. Can filter by platform and version state.",
|
|
2551
|
+
inputSchema: {
|
|
2552
|
+
type: "object",
|
|
2553
|
+
properties: {
|
|
2554
|
+
appId: {
|
|
2555
|
+
type: "string",
|
|
2556
|
+
description: "The App Store Connect app ID"
|
|
2557
|
+
},
|
|
2558
|
+
platform: {
|
|
2559
|
+
type: "string",
|
|
2560
|
+
description: "Filter by platform (IOS, MAC_OS, TV_OS, VISION_OS)",
|
|
2561
|
+
enum: ["IOS", "MAC_OS", "TV_OS", "VISION_OS"]
|
|
2562
|
+
},
|
|
2563
|
+
versionState: {
|
|
2564
|
+
type: "string",
|
|
2565
|
+
description: "Filter by version state (e.g., PREPARE_FOR_SUBMISSION, READY_FOR_SALE)"
|
|
2566
|
+
},
|
|
2567
|
+
limit: {
|
|
2568
|
+
type: "number",
|
|
2569
|
+
description: "Maximum number of versions to return (1-200)",
|
|
2570
|
+
minimum: 1,
|
|
2571
|
+
maximum: 200
|
|
2572
|
+
}
|
|
2573
|
+
},
|
|
2574
|
+
required: ["appId"]
|
|
2575
|
+
}
|
|
2576
|
+
},
|
|
2577
|
+
{
|
|
2578
|
+
name: "get_app_version",
|
|
2579
|
+
description: "Get detailed information about a specific app version.",
|
|
2580
|
+
inputSchema: {
|
|
2581
|
+
type: "object",
|
|
2582
|
+
properties: {
|
|
2583
|
+
versionId: {
|
|
2584
|
+
type: "string",
|
|
2585
|
+
description: "The App Store version ID"
|
|
2586
|
+
}
|
|
2587
|
+
},
|
|
2588
|
+
required: ["versionId"]
|
|
2589
|
+
}
|
|
2590
|
+
},
|
|
2591
|
+
{
|
|
2592
|
+
name: "create_app_version",
|
|
2593
|
+
description: "Create a new App Store version for an app.",
|
|
2594
|
+
inputSchema: {
|
|
2595
|
+
type: "object",
|
|
2596
|
+
properties: {
|
|
2597
|
+
appId: {
|
|
2598
|
+
type: "string",
|
|
2599
|
+
description: "The App Store Connect app ID"
|
|
2600
|
+
},
|
|
2601
|
+
platform: {
|
|
2602
|
+
type: "string",
|
|
2603
|
+
description: "Platform for the version",
|
|
2604
|
+
enum: ["IOS", "MAC_OS", "TV_OS", "VISION_OS"]
|
|
2605
|
+
},
|
|
2606
|
+
versionString: {
|
|
2607
|
+
type: "string",
|
|
2608
|
+
description: "Version number (e.g., '1.0.0', '2.1')"
|
|
2609
|
+
},
|
|
2610
|
+
releaseType: {
|
|
2611
|
+
type: "string",
|
|
2612
|
+
description: "Release type",
|
|
2613
|
+
enum: ["MANUAL", "AFTER_APPROVAL", "SCHEDULED"]
|
|
2614
|
+
},
|
|
2615
|
+
copyright: {
|
|
2616
|
+
type: "string",
|
|
2617
|
+
description: "Copyright text for the version"
|
|
2618
|
+
},
|
|
2619
|
+
earliestReleaseDate: {
|
|
2620
|
+
type: "string",
|
|
2621
|
+
description: "Earliest release date (ISO 8601 format) for SCHEDULED release type"
|
|
2622
|
+
}
|
|
2623
|
+
},
|
|
2624
|
+
required: ["appId", "platform", "versionString"]
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
];
|
|
2628
|
+
|
|
2629
|
+
// src/tools/index.ts
|
|
2630
|
+
var toolHandlers = {
|
|
2631
|
+
// Apps
|
|
2632
|
+
list_apps: listApps,
|
|
2633
|
+
get_app: getApp,
|
|
2634
|
+
// Versions
|
|
2635
|
+
list_app_versions: listAppVersions,
|
|
2636
|
+
get_app_version: getAppVersion,
|
|
2637
|
+
create_app_version: createAppVersion,
|
|
2638
|
+
// Localizations
|
|
2639
|
+
list_version_localizations: listVersionLocalizations,
|
|
2640
|
+
get_version_localization: getVersionLocalization,
|
|
2641
|
+
create_version_localization: createVersionLocalization,
|
|
2642
|
+
update_version_localization: updateVersionLocalization,
|
|
2643
|
+
delete_version_localization: deleteVersionLocalization,
|
|
2644
|
+
// App Info
|
|
2645
|
+
list_app_infos: listAppInfos,
|
|
2646
|
+
list_app_info_localizations: listAppInfoLocalizations,
|
|
2647
|
+
update_app_info_localization: updateAppInfoLocalization,
|
|
2648
|
+
// Beta
|
|
2649
|
+
list_beta_groups: listBetaGroups,
|
|
2650
|
+
list_beta_testers: listBetaTesters,
|
|
2651
|
+
add_beta_tester: addBetaTester,
|
|
2652
|
+
remove_beta_tester: removeBetaTester,
|
|
2653
|
+
// Screenshots
|
|
2654
|
+
list_screenshot_sets: listScreenshotSets,
|
|
2655
|
+
list_screenshots: listScreenshots,
|
|
2656
|
+
upload_screenshot: uploadScreenshot,
|
|
2657
|
+
// Bundle IDs
|
|
2658
|
+
list_bundle_ids: listBundleIds,
|
|
2659
|
+
get_bundle_id: getBundleId,
|
|
2660
|
+
create_bundle_id: createBundleId,
|
|
2661
|
+
update_bundle_id: updateBundleId,
|
|
2662
|
+
delete_bundle_id: deleteBundleId,
|
|
2663
|
+
// Devices
|
|
2664
|
+
list_devices: listDevices,
|
|
2665
|
+
get_device: getDevice,
|
|
2666
|
+
// Users
|
|
2667
|
+
list_users: listUsers,
|
|
2668
|
+
get_user: getUser,
|
|
2669
|
+
// Builds
|
|
2670
|
+
list_builds: listBuilds,
|
|
2671
|
+
get_build: getBuild,
|
|
2672
|
+
list_beta_tester_invitations: listBetaTesterInvitations,
|
|
2673
|
+
// Categories
|
|
2674
|
+
list_app_categories: listAppCategories,
|
|
2675
|
+
get_app_price_schedule: getAppPriceSchedule,
|
|
2676
|
+
get_app_availability: getAppAvailability
|
|
2677
|
+
};
|
|
2678
|
+
var allToolDefinitions = [
|
|
2679
|
+
...appsToolDefinitions,
|
|
2680
|
+
...versionsToolDefinitions,
|
|
2681
|
+
...localizationsToolDefinitions,
|
|
2682
|
+
...appInfoToolDefinitions,
|
|
2683
|
+
...betaToolDefinitions,
|
|
2684
|
+
...screenshotsToolDefinitions,
|
|
2685
|
+
...bundleIdsToolDefinitions,
|
|
2686
|
+
...devicesToolDefinitions,
|
|
2687
|
+
...usersToolDefinitions,
|
|
2688
|
+
...buildsToolDefinitions,
|
|
2689
|
+
...categoriesToolDefinitions
|
|
2690
|
+
];
|
|
2691
|
+
function hasToolHandler(name) {
|
|
2692
|
+
return name in toolHandlers;
|
|
2693
|
+
}
|
|
2694
|
+
async function executeTool(client, name, input) {
|
|
2695
|
+
const handler = toolHandlers[name];
|
|
2696
|
+
if (!handler) {
|
|
2697
|
+
return {
|
|
2698
|
+
success: false,
|
|
2699
|
+
error: {
|
|
2700
|
+
code: "UNKNOWN_TOOL",
|
|
2701
|
+
message: `Unknown tool: ${name}`
|
|
2702
|
+
}
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
return handler(client, input);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// src/index.ts
|
|
2709
|
+
var SERVER_NAME = "asc-mcp";
|
|
2710
|
+
var SERVER_VERSION = "1.0.0";
|
|
2711
|
+
async function main() {
|
|
2712
|
+
let tokenManager;
|
|
2713
|
+
let client;
|
|
2714
|
+
try {
|
|
2715
|
+
tokenManager = createTokenManagerFromEnv();
|
|
2716
|
+
client = createClient(tokenManager);
|
|
2717
|
+
} catch (error) {
|
|
2718
|
+
if (error instanceof ConfigError) {
|
|
2719
|
+
console.error(`Configuration error: ${error.message}`);
|
|
2720
|
+
console.error("\nRequired environment variables:");
|
|
2721
|
+
console.error(" APP_STORE_CONNECT_KEY_ID - Your API key ID");
|
|
2722
|
+
console.error(" APP_STORE_CONNECT_ISSUER_ID - Your issuer ID");
|
|
2723
|
+
console.error(" APP_STORE_CONNECT_P8_PATH - Path to your .p8 private key file");
|
|
2724
|
+
console.error(" or APP_STORE_CONNECT_P8_CONTENT - Content of your .p8 private key");
|
|
2725
|
+
process.exit(1);
|
|
2726
|
+
}
|
|
2727
|
+
throw error;
|
|
2728
|
+
}
|
|
2729
|
+
const server = new Server(
|
|
2730
|
+
{
|
|
2731
|
+
name: SERVER_NAME,
|
|
2732
|
+
version: SERVER_VERSION
|
|
2733
|
+
},
|
|
2734
|
+
{
|
|
2735
|
+
capabilities: {
|
|
2736
|
+
tools: {}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
);
|
|
2740
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2741
|
+
return {
|
|
2742
|
+
tools: allToolDefinitions
|
|
2743
|
+
};
|
|
2744
|
+
});
|
|
2745
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2746
|
+
const { name, arguments: args } = request.params;
|
|
2747
|
+
if (!hasToolHandler(name)) {
|
|
2748
|
+
return {
|
|
2749
|
+
content: [
|
|
2750
|
+
{
|
|
2751
|
+
type: "text",
|
|
2752
|
+
text: JSON.stringify(
|
|
2753
|
+
{
|
|
2754
|
+
success: false,
|
|
2755
|
+
error: {
|
|
2756
|
+
code: "UNKNOWN_TOOL",
|
|
2757
|
+
message: `Unknown tool: ${name}`
|
|
2758
|
+
}
|
|
2759
|
+
},
|
|
2760
|
+
null,
|
|
2761
|
+
2
|
|
2762
|
+
)
|
|
2763
|
+
}
|
|
2764
|
+
],
|
|
2765
|
+
isError: true
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
try {
|
|
2769
|
+
const result = await executeTool(client, name, args ?? {});
|
|
2770
|
+
const isError = typeof result === "object" && result !== null && "success" in result && result.success === false;
|
|
2771
|
+
return {
|
|
2772
|
+
content: [
|
|
2773
|
+
{
|
|
2774
|
+
type: "text",
|
|
2775
|
+
text: JSON.stringify(result, null, 2)
|
|
2776
|
+
}
|
|
2777
|
+
],
|
|
2778
|
+
isError
|
|
2779
|
+
};
|
|
2780
|
+
} catch (error) {
|
|
2781
|
+
const errorResponse = formatErrorResponse(error);
|
|
2782
|
+
return {
|
|
2783
|
+
content: [
|
|
2784
|
+
{
|
|
2785
|
+
type: "text",
|
|
2786
|
+
text: JSON.stringify(errorResponse, null, 2)
|
|
2787
|
+
}
|
|
2788
|
+
],
|
|
2789
|
+
isError: true
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
});
|
|
2793
|
+
const shutdown = () => {
|
|
2794
|
+
console.error("Shutting down...");
|
|
2795
|
+
tokenManager.destroy();
|
|
2796
|
+
setTimeout(() => {
|
|
2797
|
+
process.exit(0);
|
|
2798
|
+
}, 100);
|
|
2799
|
+
};
|
|
2800
|
+
process.on("SIGINT", shutdown);
|
|
2801
|
+
process.on("SIGTERM", shutdown);
|
|
2802
|
+
const transport = new StdioServerTransport();
|
|
2803
|
+
await server.connect(transport);
|
|
2804
|
+
console.error(`${SERVER_NAME} v${SERVER_VERSION} started`);
|
|
2805
|
+
}
|
|
2806
|
+
main().catch((error) => {
|
|
2807
|
+
console.error("Fatal error:", error);
|
|
2808
|
+
if (error instanceof Error && error.stack) {
|
|
2809
|
+
console.error(error.stack);
|
|
2810
|
+
}
|
|
2811
|
+
process.exit(1);
|
|
2812
|
+
});
|
|
2813
|
+
//# sourceMappingURL=index.js.map
|