@yoonion/mimi-seed-mcp 0.3.6 โ†’ 0.3.8

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.
@@ -0,0 +1,4 @@
1
+ import { JWT } from 'google-auth-library';
2
+ export declare function getServiceAccountJson(): string | null;
3
+ export declare function saveServiceAccountJson(json: string): void;
4
+ export declare function getServiceAccountClient(): JWT | null;
@@ -0,0 +1,37 @@
1
+ import { JWT } from 'google-auth-library';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ const SA_PATH = path.join(os.homedir(), '.mimi-seed', 'play-service-account.json');
6
+ export function getServiceAccountJson() {
7
+ if (!fs.existsSync(SA_PATH))
8
+ return null;
9
+ try {
10
+ return fs.readFileSync(SA_PATH, 'utf-8');
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ export function saveServiceAccountJson(json) {
17
+ const dir = path.dirname(SA_PATH);
18
+ if (!fs.existsSync(dir))
19
+ fs.mkdirSync(dir, { recursive: true });
20
+ fs.writeFileSync(SA_PATH, json, { mode: 0o600 });
21
+ }
22
+ export function getServiceAccountClient() {
23
+ const json = getServiceAccountJson();
24
+ if (!json)
25
+ return null;
26
+ try {
27
+ const parsed = JSON.parse(json);
28
+ return new JWT({
29
+ email: parsed.client_email,
30
+ key: parsed.private_key,
31
+ scopes: ['https://www.googleapis.com/auth/androidpublisher'],
32
+ });
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ import { saveServiceAccountJson, getServiceAccountJson } from './playstore-auth.js';
3
+ import readline from 'node:readline';
4
+ import fs from 'node:fs';
5
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6
+ const ask = (q) => new Promise((r) => rl.question(q, r));
7
+ async function main() {
8
+ console.log('');
9
+ console.log(' ๐Ÿค– Mimi Seed โ€” Google Play ์„œ๋น„์Šค ๊ณ„์ • ์—ฐ๊ฒฐ');
10
+ console.log('');
11
+ const existing = getServiceAccountJson();
12
+ if (existing) {
13
+ try {
14
+ const parsed = JSON.parse(existing);
15
+ console.log(` โœ… ์ด๋ฏธ ์—ฐ๊ฒฐ๋จ (${parsed.client_email ?? '?'})`);
16
+ }
17
+ catch {
18
+ console.log(' โš ๏ธ ์ €์žฅ๋œ ์„œ๋น„์Šค ๊ณ„์ •์ด ์žˆ์ง€๋งŒ ํŒŒ์‹ฑ ์˜ค๋ฅ˜');
19
+ }
20
+ const answer = await ask(' ๋‹ค์‹œ ์„ค์ •ํ• ๋ž˜? (y/N): ');
21
+ if (answer.toLowerCase() !== 'y') {
22
+ rl.close();
23
+ return;
24
+ }
25
+ }
26
+ console.log(' Google Play ์„œ๋น„์Šค ๊ณ„์ • ํ‚ค JSON ํŒŒ์ผ์ด ํ•„์š”ํ•ด:');
27
+ console.log(' 1. Google Cloud Console โ†’ IAM & Admin โ†’ Service Accounts');
28
+ console.log(' 2. ์„œ๋น„์Šค ๊ณ„์ • ์„ ํƒ โ†’ Keys โ†’ Add Key โ†’ Create new key โ†’ JSON');
29
+ console.log(' 3. ๋‹ค์šด๋กœ๋“œํ•œ JSON ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•ด');
30
+ console.log('');
31
+ const jsonPath = await ask(' ์„œ๋น„์Šค ๊ณ„์ • JSON ํŒŒ์ผ ๊ฒฝ๋กœ: ');
32
+ const trimmedPath = jsonPath.trim().replace(/^["']|["']$/g, '');
33
+ if (!fs.existsSync(trimmedPath)) {
34
+ console.log(` โŒ ํŒŒ์ผ ์—†์Œ: ${trimmedPath}`);
35
+ rl.close();
36
+ process.exit(1);
37
+ }
38
+ const json = fs.readFileSync(trimmedPath, 'utf-8');
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(json);
42
+ }
43
+ catch {
44
+ console.log(' โŒ JSON ํŒŒ์‹ฑ ์‹คํŒจ โ€” ์˜ฌ๋ฐ”๋ฅธ ์„œ๋น„์Šค ๊ณ„์ • ํ‚ค ํŒŒ์ผ์ธ์ง€ ํ™•์ธํ•ด์ค˜');
45
+ rl.close();
46
+ process.exit(1);
47
+ }
48
+ if (parsed.type !== 'service_account' || !parsed.client_email || !parsed.private_key) {
49
+ console.log(' โŒ ์„œ๋น„์Šค ๊ณ„์ • JSON ํ˜•์‹์ด ์•„๋‹ˆ์•ผ.');
50
+ console.log(' type="service_account", client_email, private_key ํ•„๋“œ๊ฐ€ ์žˆ์–ด์•ผ ํ•ด.');
51
+ rl.close();
52
+ process.exit(1);
53
+ }
54
+ saveServiceAccountJson(json);
55
+ console.log('');
56
+ console.log(` โœ… ์ €์žฅ ์™„๋ฃŒ! (${parsed.client_email})`);
57
+ console.log('');
58
+ console.log(' ์ด์ œ Claude Code์—์„œ:');
59
+ console.log(' "๋‚ด Play ์Šคํ† ์–ด ์•ฑ ๋ฆฌ์ŠคํŒ… ๋ณด์—ฌ์ค˜"');
60
+ console.log(' "Play ๊ตฌ๋… ์ƒํ’ˆ ๋ชฉ๋ก ๋ณด์—ฌ์ค˜"');
61
+ console.log('');
62
+ console.log(' โš ๏ธ Play Console ๊ถŒํ•œ ํ™•์ธ:');
63
+ console.log(' Play Console โ†’ Users and permissions โ†’ ์ด ์„œ๋น„์Šค ๊ณ„์ • ์ถ”๊ฐ€');
64
+ console.log(' ์•ฑ ๊ถŒํ•œ์—์„œ "View financial data" + "Manage store listing" ์ฒดํฌ');
65
+ console.log('');
66
+ rl.close();
67
+ }
68
+ main();
package/dist/index.js CHANGED
@@ -3,6 +3,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { z } from 'zod';
5
5
  import { getAuthenticatedClient, getStoredTokens, startAuth } from './auth/google-auth.js';
6
+ import { getServiceAccountClient, getServiceAccountJson } from './auth/playstore-auth.js';
7
+ import { getAppStoreCredentials } from './appstore/auth.js';
8
+ import { createGoogleOneTimePurchase, createGoogleSubscription, updateGoogleProduct, deleteGoogleProduct, listGoogleProducts, createAppleOneTimePurchase, createAppleSubscription, updateAppleProduct, deleteAppleProduct, listAppleProducts, } from '@onesub/providers';
6
9
  import { MIMI_SEED_CLIENT_ID, MIMI_SEED_CLIENT_SECRET } from './auth/constants.js';
7
10
  import * as firebase from './firebase/tools.js';
8
11
  import * as admob from './admob/tools.js';
@@ -35,6 +38,44 @@ function requireAuth() {
35
38
  }
36
39
  return client;
37
40
  }
41
+ const PLAY_AUTH_HINT = [
42
+ 'โŒ Google Play ์„œ๋น„์Šค ๊ณ„์ •์ด ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•˜์–ด.',
43
+ '',
44
+ 'ํ„ฐ๋ฏธ๋„์—์„œ ์ด๊ฒƒ๋งŒ ์‹คํ–‰ํ•˜๋ฉด ๋ผ:',
45
+ '',
46
+ ' npx -y @yoonion/mimi-seed-mcp mimi-seed-playstore-auth',
47
+ '',
48
+ '์„œ๋น„์Šค ๊ณ„์ • JSON ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์ €์žฅ ์™„๋ฃŒ.',
49
+ '๊ทธ ๋‹ค์Œ์— ๋‹ค์‹œ ๋ฌผ์–ด๋ด์ค˜.',
50
+ ].join('\n');
51
+ const APPSTORE_AUTH_HINT = [
52
+ 'โŒ App Store Connect ์ธ์ฆ์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์–ด.',
53
+ '',
54
+ 'ํ„ฐ๋ฏธ๋„์—์„œ ์ด๊ฒƒ๋งŒ ์‹คํ–‰ํ•˜๋ฉด ๋ผ:',
55
+ '',
56
+ ' npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth',
57
+ '',
58
+ 'Issuer ID, Key ID, .p8 ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์ €์žฅ ์™„๋ฃŒ.',
59
+ '๊ทธ ๋‹ค์Œ์— ๋‹ค์‹œ ๋ฌผ์–ด๋ด์ค˜.',
60
+ ].join('\n');
61
+ function requirePlayStoreAuth() {
62
+ const client = getServiceAccountClient();
63
+ if (!client)
64
+ throw new Error(PLAY_AUTH_HINT);
65
+ return client;
66
+ }
67
+ function requireServiceAccountJson() {
68
+ const json = getServiceAccountJson();
69
+ if (!json)
70
+ throw new Error(PLAY_AUTH_HINT);
71
+ return json;
72
+ }
73
+ function requireAppStoreCreds() {
74
+ const creds = getAppStoreCredentials();
75
+ if (!creds)
76
+ throw new Error(APPSTORE_AUTH_HINT);
77
+ return creds;
78
+ }
38
79
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
39
80
  // Firebase Tools
40
81
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@@ -258,7 +299,7 @@ server.tool('admob_create_ad_unit', 'AdMob ๊ด‘๊ณ  ๋‹จ์œ„ ์ƒ์„ฑ (v1beta โ€” Limi
258
299
  // Google Play Store Tools
259
300
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
260
301
  server.tool('playstore_get_app', 'Google Play ์•ฑ ์ƒ์„ธ ์ •๋ณด ์กฐํšŒ', { packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช… (์˜ˆ: com.findthem.app)') }, async ({ packageName }) => {
261
- const auth = requireAuth();
302
+ const auth = requirePlayStoreAuth();
262
303
  const details = await playstore.getAppDetails(auth, packageName);
263
304
  return { content: [{ type: 'text', text: JSON.stringify(details, null, 2) }] };
264
305
  });
@@ -266,7 +307,7 @@ server.tool('playstore_get_listing', 'Google Play ์Šคํ† ์–ด ๋ฆฌ์ŠคํŒ… ์กฐํšŒ (
266
307
  packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช…'),
267
308
  language: z.string().default('ko-KR').describe('์–ธ์–ด ์ฝ”๋“œ (๊ธฐ๋ณธ: ko-KR)'),
268
309
  }, async ({ packageName, language }) => {
269
- const auth = requireAuth();
310
+ const auth = requirePlayStoreAuth();
270
311
  const listing = await playstore.getListing(auth, packageName, language);
271
312
  return { content: [{ type: 'text', text: JSON.stringify(listing, null, 2) }] };
272
313
  });
@@ -277,14 +318,14 @@ server.tool('playstore_update_listing', 'Google Play ์Šคํ† ์–ด ๋ฆฌ์ŠคํŒ… ์ˆ˜์ •
277
318
  shortDescription: z.string().optional().describe('์งง์€ ์„ค๋ช… (80์ž ์ด๋‚ด)'),
278
319
  fullDescription: z.string().optional().describe('์ „์ฒด ์„ค๋ช… (4000์ž ์ด๋‚ด)'),
279
320
  }, async ({ packageName, language, title, shortDescription, fullDescription }) => {
280
- const auth = requireAuth();
321
+ const auth = requirePlayStoreAuth();
281
322
  const result = await playstore.updateListing(auth, packageName, language, {
282
323
  title, shortDescription, fullDescription,
283
324
  });
284
325
  return { content: [{ type: 'text', text: `์ˆ˜์ • ์™„๋ฃŒ:\n${JSON.stringify(result, null, 2)}` }] };
285
326
  });
286
327
  server.tool('playstore_list_tracks', 'Google Play ๋ฆด๋ฆฌ์Šค ํŠธ๋ž™ ํ˜„ํ™ฉ (ํ”„๋กœ๋•์…˜/๋ฒ ํƒ€/์•ŒํŒŒ/๋‚ด๋ถ€)', { packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช…') }, async ({ packageName }) => {
287
- const auth = requireAuth();
328
+ const auth = requirePlayStoreAuth();
288
329
  const tracks = await playstore.listTracks(auth, packageName);
289
330
  return { content: [{ type: 'text', text: JSON.stringify(tracks, null, 2) }] };
290
331
  });
@@ -293,7 +334,7 @@ server.tool('playstore_list_images', 'Google Play ๋ฆฌ์ŠคํŒ… ์ด๋ฏธ์ง€ ๋ชฉ๋ก ์กฐ
293
334
  language: z.string().describe('์–ธ์–ด ์ฝ”๋“œ (์˜ˆ: ko-KR)'),
294
335
  imageType: z.enum(['featureGraphic', 'icon', 'phoneScreenshots', 'promoGraphic', 'sevenInchScreenshots', 'tenInchScreenshots', 'tvBanner', 'tvScreenshots', 'wearScreenshots']).describe('์ด๋ฏธ์ง€ ํƒ€์ž…'),
295
336
  }, async ({ packageName, language, imageType }) => {
296
- const auth = requireAuth();
337
+ const auth = requirePlayStoreAuth();
297
338
  const images = await playstore.listImages(auth, packageName, language, imageType);
298
339
  return { content: [{ type: 'text', text: JSON.stringify(images, null, 2) }] };
299
340
  });
@@ -303,7 +344,7 @@ server.tool('playstore_upload_image', 'Google Play ๋ฆฌ์ŠคํŒ… ์ด๋ฏธ์ง€ ๋‹จ์ผ
303
344
  imageType: z.enum(['featureGraphic', 'icon', 'phoneScreenshots', 'promoGraphic', 'sevenInchScreenshots', 'tenInchScreenshots', 'tvBanner', 'tvScreenshots', 'wearScreenshots']),
304
345
  filePath: z.string().describe('์—…๋กœ๋“œํ•  ์ด๋ฏธ์ง€ ์ ˆ๋Œ€ ๊ฒฝ๋กœ'),
305
346
  }, async ({ packageName, language, imageType, filePath }) => {
306
- const auth = requireAuth();
347
+ const auth = requirePlayStoreAuth();
307
348
  const result = await playstore.uploadImage(auth, packageName, language, imageType, filePath);
308
349
  return { content: [{ type: 'text', text: `โœ… ์—…๋กœ๋“œ ์™„๋ฃŒ\n${JSON.stringify(result, null, 2)}` }] };
309
350
  });
@@ -312,7 +353,7 @@ server.tool('playstore_delete_all_images', 'Google Play ๋ฆฌ์ŠคํŒ… ํŠน์ • imageTy
312
353
  language: z.string().describe('์–ธ์–ด ์ฝ”๋“œ'),
313
354
  imageType: z.enum(['featureGraphic', 'icon', 'phoneScreenshots', 'promoGraphic', 'sevenInchScreenshots', 'tenInchScreenshots', 'tvBanner', 'tvScreenshots', 'wearScreenshots']),
314
355
  }, async ({ packageName, language, imageType }) => {
315
- const auth = requireAuth();
356
+ const auth = requirePlayStoreAuth();
316
357
  const result = await playstore.deleteAllImages(auth, packageName, language, imageType);
317
358
  return { content: [{ type: 'text', text: `โœ… ์ „์ฒด ์‚ญ์ œ\n${JSON.stringify(result, null, 2)}` }] };
318
359
  });
@@ -322,7 +363,7 @@ server.tool('playstore_replace_images', 'Google Play ๋ฆฌ์ŠคํŒ… ์ด๋ฏธ์ง€ ์ผ๊ด„
322
363
  imageType: z.enum(['featureGraphic', 'icon', 'phoneScreenshots', 'promoGraphic', 'sevenInchScreenshots', 'tenInchScreenshots', 'tvBanner', 'tvScreenshots', 'wearScreenshots']),
323
364
  filePaths: z.array(z.string()).describe('์—…๋กœ๋“œํ•  ์ด๋ฏธ์ง€ ์ ˆ๋Œ€ ๊ฒฝ๋กœ ๋ฐฐ์—ด (์ˆœ์„œ = ๋…ธ์ถœ ์ˆœ์„œ)'),
324
365
  }, async ({ packageName, language, imageType, filePaths }) => {
325
- const auth = requireAuth();
366
+ const auth = requirePlayStoreAuth();
326
367
  const result = await playstore.replaceImages(auth, packageName, language, imageType, filePaths);
327
368
  return { content: [{ type: 'text', text: `โœ… ${result.count}์žฅ ๊ต์ฒด ์™„๋ฃŒ\n${JSON.stringify(result, null, 2)}` }] };
328
369
  });
@@ -333,7 +374,7 @@ server.tool('playstore_update_release_notes', "Google Play ํŠธ๋ž™ ๋ฆด๋ฆฌ์Šค์˜ '
333
374
  language: z.string().describe('์–ธ์–ด ์ฝ”๋“œ (์˜ˆ: ko-KR, en-US)'),
334
375
  text: z.string().describe('๋ฆด๋ฆฌ์Šค ๋…ธํŠธ ๋ณธ๋ฌธ (500์ž ์ด๋‚ด)'),
335
376
  }, async ({ packageName, track, versionCode, language, text }) => {
336
- const auth = requireAuth();
377
+ const auth = requirePlayStoreAuth();
337
378
  const result = await playstore.updateReleaseNotes(auth, packageName, track, versionCode, language, text);
338
379
  return { content: [{ type: 'text', text: `โœ… ${packageName} ${track} v${versionCode} ${language} ๋…ธํŠธ ๋ฐ˜์˜\n\n${JSON.stringify(result, null, 2)}` }] };
339
380
  });
@@ -343,12 +384,12 @@ server.tool('playstore_update_latest_release_notes', "Google Play ํŠธ๋ž™์˜ ์ตœ
343
384
  language: z.string().describe('์–ธ์–ด ์ฝ”๋“œ (์˜ˆ: ko-KR)'),
344
385
  text: z.string().describe('๋ฆด๋ฆฌ์Šค ๋…ธํŠธ ๋ณธ๋ฌธ (500์ž ์ด๋‚ด)'),
345
386
  }, async ({ packageName, track, language, text }) => {
346
- const auth = requireAuth();
387
+ const auth = requirePlayStoreAuth();
347
388
  const result = await playstore.updateLatestReleaseNotes(auth, packageName, track, language, text);
348
389
  return { content: [{ type: 'text', text: `โœ… ${packageName} ${track} (versionCodes=${JSON.stringify(result.updatedVersionCodes)}) ${language} ๋…ธํŠธ ๋ฐ˜์˜\n\n${JSON.stringify(result, null, 2)}` }] };
349
390
  });
350
391
  server.tool('playstore_list_reviews', 'Google Play ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํšŒ', { packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช…') }, async ({ packageName }) => {
351
- const auth = requireAuth();
392
+ const auth = requirePlayStoreAuth();
352
393
  const reviews = await playstore.listReviews(auth, packageName);
353
394
  return { content: [{ type: 'text', text: JSON.stringify(reviews, null, 2) }] };
354
395
  });
@@ -357,124 +398,121 @@ server.tool('playstore_reply_review', 'Google Play ๋ฆฌ๋ทฐ์— ๋‹ต๋ณ€', {
357
398
  reviewId: z.string().describe('๋ฆฌ๋ทฐ ID'),
358
399
  replyText: z.string().describe('๋‹ต๋ณ€ ๋‚ด์šฉ'),
359
400
  }, async ({ packageName, reviewId, replyText }) => {
360
- const auth = requireAuth();
401
+ const auth = requirePlayStoreAuth();
361
402
  const result = await playstore.replyToReview(auth, packageName, reviewId, replyText);
362
403
  return { content: [{ type: 'text', text: `๋‹ต๋ณ€ ์™„๋ฃŒ:\n${JSON.stringify(result, null, 2)}` }] };
363
404
  });
364
405
  server.tool('playstore_list_inapp_products', 'Google Play ์ธ์•ฑ ์ƒํ’ˆ ๋ชฉ๋ก', { packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช…') }, async ({ packageName }) => {
365
- const auth = requireAuth();
406
+ const auth = requirePlayStoreAuth();
366
407
  const products = await playstore.listInAppProducts(auth, packageName);
367
408
  return { content: [{ type: 'text', text: JSON.stringify(products, null, 2) }] };
368
409
  });
369
410
  server.tool('playstore_list_subscriptions', 'Google Play ๊ตฌ๋… ์ƒํ’ˆ ๋ชฉ๋ก', { packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช…') }, async ({ packageName }) => {
370
- const auth = requireAuth();
411
+ const auth = requirePlayStoreAuth();
371
412
  const subs = await playstore.listSubscriptions(auth, packageName);
372
413
  return { content: [{ type: 'text', text: JSON.stringify(subs, null, 2) }] };
373
414
  });
374
415
  server.tool('playstore_create_onetime_product', [
375
- 'Google Play์— ์ผํšŒ์„ฑ ์ธ์•ฑ ์ƒํ’ˆ์„ ์ƒ์„ฑ/๊ฐฑ์‹  (upsert).',
376
- '์ด๋ฏธ ๊ฐ™์€ productId๊ฐ€ ์žˆ์œผ๋ฉด listingsยท๊ฐ€๊ฒฉ์„ ๋ฎ์–ด์”€. ๊ฐ€๊ฒฉ์€ USD ๊ธฐ์ค€ + EUR ์ž๋™ ํ™˜์‚ฐ + newRegionsConfig๋กœ ์‹ ๊ทœ ์ง€์—ญ ์ž๋™ ์‚ฌ์šฉ.',
377
- 'Play Console ๊ถŒํ•œ: "Manage store presence" ํ•„์š”. ํ™œ์„ฑํ™”๋Š” Console์—์„œ ๋ณ„๋„ ํ† ๊ธ€.',
416
+ 'Google Play์— ์ผํšŒ์„ฑ ์ธ์•ฑ ์ƒํ’ˆ(์†Œ๋น„์„ฑ ๋˜๋Š” ๋น„์†Œ๋น„์„ฑ)์„ ์ƒ์„ฑ.',
417
+ 'Play Console ๊ถŒํ•œ: "Manage store presence" ํ•„์š”. ์ƒ์„ฑ ํ›„ Console์—์„œ ํ™œ์„ฑํ™” ํ•„์š”.',
378
418
  ].join(' '), {
379
419
  packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช… (์˜ˆ: com.example.app)'),
380
420
  productId: z
381
421
  .string()
382
422
  .describe('์ƒํ’ˆ ID (์†Œ๋ฌธ์ž/์ˆซ์ž/์–ธ๋”์Šค์ฝ”์–ด/์ , ์˜ˆ: premium_unlock). ํ•œ ๋ฒˆ ์ •ํ•˜๋ฉด ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€.'),
383
- title: z.string().describe('์ƒํ’ˆ ์ œ๋ชฉ (์ตœ๋Œ€ 55์ž)'),
384
- description: z.string().describe('์ƒํ’ˆ ์„ค๋ช… (์ตœ๋Œ€ 200์ž)'),
385
- priceUsd: z.number().describe('USD ๊ฐ€๊ฒฉ (์˜ˆ: 4.99)'),
386
- languageCode: z.string().optional().describe('listing ์–ธ์–ด (๊ธฐ๋ณธ en-US)'),
387
- purchaseOptionId: z.string().optional().describe('๊ตฌ๋งค ์˜ต์…˜ ID (๊ธฐ๋ณธ default-buy)'),
388
- legacyCompatible: z
389
- .boolean()
390
- .optional()
391
- .describe('๊ตฌ PBL ํด๋ผ์ด์–ธํŠธ ํ˜ธํ™˜ (๊ธฐ๋ณธ true)'),
392
- newRegionsPricing: z
393
- .object({
394
- usdPrice: z.number().describe('Play๊ฐ€ ์‹ ๊ทœ ์ง€์—ญ launch ์‹œ ์‚ฌ์šฉํ•  USD ๊ฐ€๊ฒฉ'),
395
- eurPrice: z.number().describe('Play๊ฐ€ ์‹ ๊ทœ ์ง€์—ญ launch ์‹œ ์‚ฌ์šฉํ•  EUR ๊ฐ€๊ฒฉ'),
396
- })
423
+ name: z.string().describe('์ƒํ’ˆ ์ด๋ฆ„ (์Šคํ† ์–ด ๋…ธ์ถœ ์ œ๋ชฉ)'),
424
+ price: z.number().int().describe('์ฃผ ํ†ตํ™” ๊ธฐ์ค€ ๊ฐ€๊ฒฉ (์ตœ์†Œ ๋‹จ์œ„: USD/EUR์ด๋ฉด cents, KRW/JPY์ด๋ฉด ์›ํ™” ์ •์ˆ˜. ์˜ˆ: USD $4.99 โ†’ 499, KRW โ‚ฉ5,900 โ†’ 5900)'),
425
+ currency: z.string().default('USD').describe('ISO 4217 ํ†ตํ™” ์ฝ”๋“œ (๊ธฐ๋ณธ USD)'),
426
+ type: z
427
+ .enum(['consumable', 'non_consumable'])
428
+ .default('non_consumable')
429
+ .describe('์ƒํ’ˆ ์œ ํ˜• (์†Œ๋น„์„ฑ/๋น„์†Œ๋น„์„ฑ). ์•ฑ์ด consumePurchase ํ˜ธ์ถœํ•˜๋ฉด ์†Œ๋น„์„ฑ.'),
430
+ extraRegions: z
431
+ .array(z.object({
432
+ currency: z.string().describe('ISO 4217 ํ†ตํ™” ์ฝ”๋“œ (์˜ˆ: KRW, JPY, GBP)'),
433
+ price: z.number().describe('๊ฐ€๊ฒฉ (์ตœ์†Œ ๋‹จ์œ„: KRW โ‚ฉ1,100 โ†’ 1100, USD $0.99 โ†’ 99)'),
434
+ }))
397
435
  .optional()
398
- .describe('์‹ ๊ทœ ์ถœ์‹œ ์ง€์—ญ์—์„œ ์ž๋™ ์‚ฌ์šฉํ•  ๊ฐ€๊ฒฉ (์„ ํƒ). ๋ฏธ์ง€์ • ์‹œ ์‹ ๊ทœ ์ง€์—ญ OFF.'),
436
+ .describe('์ถ”๊ฐ€ ์ง€์—ญ๋ณ„ ๋ช…์‹œ ๊ฐ€๊ฒฉ. ์ž๋™ ํ™˜์‚ฐ์ด ๋ถ€์ •ํ™•ํ•œ KRW/JPY ๋“ฑ์— ์ง์ ‘ ์ง€์ •.'),
399
437
  }, async (args) => {
400
- const auth = requireAuth();
401
- const result = await playstore.createOnetimeProduct(auth, args);
438
+ const json = requireServiceAccountJson();
439
+ const result = await createGoogleOneTimePurchase({
440
+ packageName: args.packageName,
441
+ productId: args.productId,
442
+ name: args.name,
443
+ price: args.price,
444
+ currency: args.currency,
445
+ type: args.type,
446
+ ...(args.extraRegions && { extraRegions: args.extraRegions }),
447
+ serviceAccountKey: json,
448
+ });
449
+ if (!result.success) {
450
+ return { content: [{ type: 'text', text: `โŒ ์ƒํ’ˆ ์ƒ์„ฑ ์‹คํŒจ: ${result.error}` }] };
451
+ }
402
452
  return {
403
- content: [
404
- {
453
+ content: [{
405
454
  type: 'text',
406
455
  text: [
407
- `โœ“ Play ์ผํšŒ์„ฑ ์ƒํ’ˆ upsert ์™„๋ฃŒ`,
408
- `productId: ${args.productId}`,
409
- `price: $${args.priceUsd} (US)`,
410
- args.newRegionsPricing
411
- ? `์‹ ๊ทœ ์ง€์—ญ: $${args.newRegionsPricing.usdPrice} / โ‚ฌ${args.newRegionsPricing.eurPrice}`
412
- : '์‹ ๊ทœ ์ง€์—ญ: OFF (newRegionsPricing ๋ฏธ์ง€์ •)',
413
- 'โš  ๊ธฐ์กด์— ๋‹ค๊ตญ์–ด/๋‹ค์ง€์—ญ ๋“ฑ๋ก๋œ ์ƒํ’ˆ์ด๋ฉด listingsยทpurchaseOptions๊ฐ€ ํ†ต์งธ ๊ต์ฒด๋จ.',
456
+ `โœ“ Play ์ผํšŒ์„ฑ ์ƒํ’ˆ ์ƒ์„ฑ ์™„๋ฃŒ`,
457
+ `productId: ${result.productId}`,
458
+ `price: ${args.price} ${args.currency}`,
414
459
  '',
415
460
  'Play Console์—์„œ ํ™œ์„ฑํ™” ํ™•์ธ:',
416
461
  `https://play.google.com/console/u/0/developers/-/app/-/managed-products?package=${encodeURIComponent(args.packageName)}`,
417
- '',
418
- '์‘๋‹ต:',
419
- JSON.stringify(result, null, 2),
420
462
  ].join('\n'),
421
- },
422
- ],
463
+ }],
423
464
  };
424
465
  });
425
466
  server.tool('playstore_create_subscription', [
426
467
  'Google Play์— ์ž๋™ ๊ฐฑ์‹  ๊ตฌ๋…์„ ์ƒ์„ฑํ•˜๊ณ  baseplan์„ ํ™œ์„ฑํ™”.',
427
- '๊ตฌ๋… ์…ธ ์ƒ์„ฑ โ†’ baseplan(๊ฐ€๊ฒฉยท์ฃผ๊ธฐ) ์ถ”๊ฐ€ โ†’ ์ž๋™ ํ™œ์„ฑํ™” (autoActivate=false๋ฉด draft).',
428
- '์ด๋ฏธ ๊ฐ™์€ productId๊ฐ€ ์žˆ์œผ๋ฉด listings ๊ฐฑ์‹  + ๋™์ผ basePlanId๊ฐ€ ์žˆ์œผ๋ฉด baseplan ์ƒ์„ฑ ๋‹จ๊ณ„ skip.',
468
+ '๊ตฌ๋… ์ƒ์„ฑ โ†’ baseplan(๊ฐ€๊ฒฉยท์ฃผ๊ธฐ) ์ถ”๊ฐ€ โ†’ ์ž๋™ ํ™œ์„ฑํ™”.',
469
+ '์ด๋ฏธ ๊ฐ™์€ productId๊ฐ€ ์žˆ์œผ๋ฉด ์ƒ์„ฑ ์‹คํŒจ (Play API ํŠน์„ฑ์ƒ upsert ๋ฏธ์ง€์›).',
429
470
  ].join(' '), {
430
471
  packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช…'),
431
472
  productId: z
432
473
  .string()
433
- .describe('๊ตฌ๋… ์ƒํ’ˆ ID (์†Œ๋ฌธ์ž/์ˆซ์ž/์–ธ๋”์Šค์ฝ”์–ด/์ , 1-40์ž, ์˜ˆ: premium_monthly)'),
434
- title: z.string().describe('๊ตฌ๋… ์ œ๋ชฉ'),
435
- description: z.string().describe('๊ตฌ๋… ์„ค๋ช…'),
436
- benefits: z.array(z.string()).optional().describe('ํ˜œํƒ bullet ๋ฆฌ์ŠคํŠธ (์„ ํƒ)'),
437
- languageCode: z.string().optional().describe('๊ธฐ๋ณธ en-US'),
438
- basePlanId: z
439
- .string()
440
- .optional()
441
- .describe('๋ฒ ์ด์Šคํ”Œ๋žœ ID (์†Œ๋ฌธ์ž/์ˆซ์ž/ํ•˜์ดํ”ˆ, ๊ธฐ๋ณธ์€ billingPeriod๋กœ ์ถ”๋ก : P1Mโ†’monthly, P1Yโ†’yearly)'),
442
- billingPeriod: z
443
- .enum(['P1W', 'P1M', 'P3M', 'P6M', 'P1Y'])
444
- .describe('์ฒญ๊ตฌ ์ฃผ๊ธฐ (ISO 8601: P1M=์›”, P1Y=๋…„)'),
445
- priceUsd: z.number().describe('USD ๊ฐ€๊ฒฉ'),
446
- gracePeriod: z
447
- .enum(['P0D', 'P3D', 'P7D', 'P14D', 'P30D'])
474
+ .describe('๊ตฌ๋… ์ƒํ’ˆ ID (์†Œ๋ฌธ์ž/์ˆซ์ž/์–ธ๋”์Šค์ฝ”์–ด/์ , ์˜ˆ: premium_monthly)'),
475
+ name: z.string().describe('๊ตฌ๋… ์ œ๋ชฉ (์Šคํ† ์–ด ๋…ธ์ถœ)'),
476
+ price: z.number().int().describe('์ฃผ ํ†ตํ™” ๊ธฐ์ค€ ๊ฐ€๊ฒฉ (์ตœ์†Œ ๋‹จ์œ„: USD cents. ์˜ˆ: $4.99 โ†’ 499, โ‚ฉ5,900 โ†’ 5900)'),
477
+ currency: z.string().default('USD').describe('ISO 4217 ํ†ตํ™” ์ฝ”๋“œ (๊ธฐ๋ณธ USD)'),
478
+ period: z
479
+ .enum(['monthly', 'yearly'])
480
+ .describe('์ฒญ๊ตฌ ์ฃผ๊ธฐ'),
481
+ extraRegions: z
482
+ .array(z.object({
483
+ currency: z.string().describe('ISO 4217 ํ†ตํ™” ์ฝ”๋“œ (์˜ˆ: KRW, JPY)'),
484
+ price: z.number().describe('๊ฐ€๊ฒฉ (์ตœ์†Œ ๋‹จ์œ„)'),
485
+ }))
448
486
  .optional()
449
- .describe('๊ฒฐ์ œ ์‹คํŒจ ์‹œ ์œ ์˜ˆ ๊ธฐ๊ฐ„ (๊ธฐ๋ณธ P7D)'),
450
- autoActivate: z
451
- .boolean()
452
- .optional()
453
- .describe('์ƒ์„ฑ ํ›„ baseplan ์ž๋™ ํ™œ์„ฑํ™” (๊ธฐ๋ณธ true). false๋ฉด Console์—์„œ ์ˆ˜๋™ ํ™œ์„ฑํ™”.'),
454
- newRegionsPricing: z
455
- .object({
456
- usdPrice: z.number(),
457
- eurPrice: z.number(),
458
- })
459
- .optional()
460
- .describe('์‹ ๊ทœ ์ถœ์‹œ ์ง€์—ญ ์ž๋™ ๊ฐ€๊ฒฉ (์„ ํƒ). ๋ฏธ์ง€์ • ์‹œ ์‹ ๊ทœ ์ง€์—ญ์—” ๋…ธ์ถœ ์•ˆ ๋จ.'),
487
+ .describe('์ถ”๊ฐ€ ์ง€์—ญ๋ณ„ ๋ช…์‹œ ๊ฐ€๊ฒฉ'),
461
488
  }, async (args) => {
462
- const auth = requireAuth();
463
- const result = await playstore.createSubscription(auth, args);
464
- const lines = [
465
- `โœ“ Play ๊ตฌ๋… ์ƒ์„ฑ ์™„๋ฃŒ`,
466
- `productId: ${result.productId}`,
467
- `basePlanId: ${result.basePlanId} (${args.billingPeriod}, $${args.priceUsd})`,
468
- result.activated
469
- ? `โœ“ baseplan ํ™œ์„ฑํ™”๋จ (state=${result.basePlanState ?? 'ACTIVE'})`
470
- : result.activateError
471
- ? `โš  baseplan ์ž๋™ ํ™œ์„ฑํ™” ์‹คํŒจ: ${result.activateError}\n โ†’ Console์—์„œ ์ˆ˜๋™ ํ™œ์„ฑํ™” ํ•„์š”.`
472
- : `โธ baseplan์€ draft ์ƒํƒœ (autoActivate=false). Console์—์„œ ํ™œ์„ฑํ™” ํ•„์š”.`,
473
- '',
474
- 'Play Console:',
475
- `https://play.google.com/console/u/0/developers/-/app/-/subscriptions?package=${encodeURIComponent(args.packageName)}`,
476
- ];
477
- return { content: [{ type: 'text', text: lines.join('\n') }] };
489
+ const json = requireServiceAccountJson();
490
+ const result = await createGoogleSubscription({
491
+ packageName: args.packageName,
492
+ productId: args.productId,
493
+ name: args.name,
494
+ price: args.price,
495
+ currency: args.currency,
496
+ period: args.period,
497
+ ...(args.extraRegions && { extraRegions: args.extraRegions }),
498
+ serviceAccountKey: json,
499
+ });
500
+ if (!result.success) {
501
+ return { content: [{ type: 'text', text: `โŒ ๊ตฌ๋… ์ƒ์„ฑ ์‹คํŒจ: ${result.error}` }] };
502
+ }
503
+ return {
504
+ content: [{
505
+ type: 'text',
506
+ text: [
507
+ `โœ“ Play ๊ตฌ๋… ์ƒ์„ฑ ์™„๋ฃŒ`,
508
+ `productId: ${result.productId}`,
509
+ `price: ${args.price} ${args.currency} / ${args.period}`,
510
+ '',
511
+ 'Play Console:',
512
+ `https://play.google.com/console/u/0/developers/-/app/-/subscriptions?package=${encodeURIComponent(args.packageName)}`,
513
+ ].join('\n'),
514
+ }],
515
+ };
478
516
  });
479
517
  server.tool('playstore_verify_service_account', [
480
518
  "์„œ๋น„์Šค ๊ณ„์ • JSON์ด ์ฃผ์–ด์ง„ packageName์— ๋Œ€ํ•ด Play Developer API ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ์ง€ + 'View financial data' ๊ถŒํ•œ๊นŒ์ง€ ์žˆ๋Š”์ง€ ๊ฒ€์ฆ.",
@@ -795,116 +833,208 @@ server.tool('appstore_reply_review', [
795
833
  });
796
834
  // --- App Store IAP / ๊ตฌ๋… ์ƒ์„ฑ ---
797
835
  server.tool('appstore_create_inapp_purchase', [
798
- 'App Store์— ์ผํšŒ์„ฑ ์ธ์•ฑ ๊ตฌ๋งค(IAP)๋ฅผ ์ƒ์„ฑ โ€” type: CONSUMABLE / NON_CONSUMABLE / NON_RENEWING_SUBSCRIPTION.',
799
- '๋‚ด๋ถ€ ํ๋ฆ„: POST /v2/inAppPurchases(draft) โ†’ POST /v1/inAppPurchaseLocalizations(displayName/description) โ†’ priceSchedule(๋งค์นญ๋˜๋Š” pricePoint) โ†’ submission(autoSubmit=true ์‹œ).',
800
- 'autoSubmit์ด ์‹คํŒจํ•ด๋„ draft + ๋กœ์ปฌ๋ผ์ด์ œ์ด์…˜ + ๊ฐ€๊ฒฉ์€ ์œ ์ง€๋จ. ์–ด๋””๊นŒ์ง€ ์ง„ํ–‰๋๋Š”์ง€ ์‘๋‹ต์— ํ‘œ์‹œ.',
801
- '์ œ์ถœ์ด ๋ง‰ํžˆ๋Š” ํ”ํ•œ ์ด์œ : ๋ฆฌ๋ทฐ ๋…ธํŠธ ๋ถ€์กฑ / ์Šคํฌ๋ฆฐ์ƒท ๋ฏธ์ฒจ๋ถ€ / ๋ถ€๋ชจ ์•ฑ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ฏธ์™„ โ€” ๊ทธ๋• ์ฝ˜์†” ๋งํฌ๋กœ ์ด๋™ํ•ด ๋งˆ๋ฌด๋ฆฌ.',
836
+ 'App Store์— ์ผํšŒ์„ฑ ์ธ์•ฑ ๊ตฌ๋งค(IAP)๋ฅผ ์ƒ์„ฑ โ€” CONSUMABLE (์†Œ๋น„์„ฑ) / NON_CONSUMABLE (๋น„์†Œ๋น„์„ฑ).',
837
+ '์ƒ์„ฑ ํ›„ App Store Connect์—์„œ ์Šคํฌ๋ฆฐ์ƒทยท๋ฆฌ๋ทฐ ๋…ธํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ์‹ฌ์‚ฌ ์ œ์ถœ ๊ฐ€๋Šฅ.',
802
838
  ].join(' '), {
803
839
  appId: z.string().describe('App Store ์•ฑ ID (appstore_list_apps ๊ฒฐ๊ณผ์˜ id, ์ˆซ์žํ˜•)'),
804
840
  productId: z
805
841
  .string()
806
842
  .describe('์ƒํ’ˆ ID (๊ธ€๋กœ๋ฒŒ unique ๊ถŒ์žฅ: ์˜ˆ com.example.coins_100)'),
807
- referenceName: z.string().describe('๋‚ด๋ถ€ ์‹๋ณ„ ์ด๋ฆ„ (Console ํ‘œ์‹œ์šฉ)'),
808
- inAppPurchaseType: z
809
- .enum(['CONSUMABLE', 'NON_CONSUMABLE', 'NON_RENEWING_SUBSCRIPTION'])
810
- .describe('IAP ์œ ํ˜•'),
811
- displayName: z.string().describe('์‚ฌ์šฉ์ž ๋…ธ์ถœ ์ด๋ฆ„ (์ตœ๋Œ€ 30์ž)'),
812
- description: z.string().describe('์‚ฌ์šฉ์ž ๋…ธ์ถœ ์„ค๋ช… (์ตœ๋Œ€ 45์ž)'),
813
- priceUsd: z.number().describe('USD ๊ฐ€๊ฒฉ (์˜ˆ: 0.99) โ€” ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด pricePoint๋ฅผ ์ž๋™ ๋งค์นญ'),
814
- locale: z.string().optional().describe('๋กœ์ปฌ๋ผ์ด์ œ์ด์…˜ ๋กœ์ผ€์ผ (๊ธฐ๋ณธ en-US)'),
815
- baseTerritory: z
816
- .string()
843
+ name: z.string().describe('์ƒํ’ˆ ์ด๋ฆ„ (์Šคํ† ์–ด ๋…ธ์ถœ, ์ตœ๋Œ€ 30์ž)'),
844
+ price: z.number().int().describe('๊ฐ€๊ฒฉ (์ตœ์†Œ ๋‹จ์œ„: USD cents. ์˜ˆ: $0.99 โ†’ 99, โ‚ฉ1,100 โ†’ 1100)'),
845
+ currency: z.string().default('USD').describe('ISO 4217 ํ†ตํ™” ์ฝ”๋“œ (๊ธฐ๋ณธ USD)'),
846
+ type: z
847
+ .enum(['consumable', 'non_consumable'])
848
+ .default('non_consumable')
849
+ .describe('IAP ์œ ํ˜• (์†Œ๋น„์„ฑ/๋น„์†Œ๋น„์„ฑ)'),
850
+ extraRegions: z
851
+ .array(z.object({
852
+ currency: z.string().describe('ISO 4217 ํ†ตํ™” ์ฝ”๋“œ (์˜ˆ: KRW)'),
853
+ price: z.number().describe('๊ฐ€๊ฒฉ (์ตœ์†Œ ๋‹จ์œ„)'),
854
+ }))
817
855
  .optional()
818
- .describe('๊ธฐ์ค€ ์ง€์—ญ ISO 3166-1 alpha-3 (๊ธฐ๋ณธ USA)'),
819
- reviewNote: z.string().optional().describe('Apple ๋ฆฌ๋ทฐ์–ด๋ฅผ ์œ„ํ•œ ๋…ธํŠธ'),
820
- familySharable: z.boolean().optional().describe('ํŒจ๋ฐ€๋ฆฌ ๊ณต์œ  ๊ฐ€๋Šฅ (๊ธฐ๋ณธ false)'),
821
- autoSubmit: z
822
- .boolean()
823
- .optional()
824
- .describe('์ƒ์„ฑ ํ›„ ์ž๋™ ์‹ฌ์‚ฌ ์ œ์ถœ ์‹œ๋„ (๊ธฐ๋ณธ true). ์‹คํŒจํ•ด๋„ draft๋Š” ์œ ์ง€.'),
856
+ .describe('์ถ”๊ฐ€ ์ง€์—ญ๋ณ„ ๋ช…์‹œ ๊ฐ€๊ฒฉ'),
857
+ bundleId: z.string().optional().describe('๋ฒˆ๋“ค ID (appId ๋Œ€์‹  ์‚ฌ์šฉ ๊ฐ€๋Šฅ)'),
825
858
  }, async (args) => {
826
- const result = await appstore.createInAppPurchase(args);
827
- const lines = [
828
- result.failedStep
829
- ? `โš  App Store IAP ์ผ๋ถ€ ๋‹จ๊ณ„ ์‹คํŒจ (failedStep=${result.failedStep})`
830
- : `โœ“ App Store IAP ์ƒ์„ฑ ์™„๋ฃŒ`,
831
- `iapId: ${result.iapId}`,
832
- `productId: ${result.productId}`,
833
- result.localizationId ? `โœ“ ๋กœ์ปฌ๋ผ์ด์ œ์ด์…˜ (${args.locale ?? 'en-US'})` : 'โธ ๋กœ์ปฌ๋ผ์ด์ œ์ด์…˜ ๋ฏธ์ง„ํ–‰',
834
- result.priceScheduleId
835
- ? `โœ“ ๊ฐ€๊ฒฉ ($${args.priceUsd} ${args.baseTerritory ?? 'USA'})`
836
- : 'โธ ๊ฐ€๊ฒฉ ๋ฏธ์„ค์ •',
837
- result.priceMatchWarning ? `โš  ${result.priceMatchWarning}` : '',
838
- result.submissionId
839
- ? `โœ“ ์‹ฌ์‚ฌ ์ œ์ถœ๋จ (state=${result.submissionState ?? 'WAITING_FOR_REVIEW'})`
840
- : args.autoSubmit === false
841
- ? 'โธ ์ž๋™ ์ œ์ถœ skip (autoSubmit=false) โ€” Console์—์„œ ์ˆ˜๋™ ์ œ์ถœ'
842
- : result.failedStep === 'submission'
843
- ? `โš  ์ž๋™ ์ œ์ถœ ์‹คํŒจ (draftยท๊ฐ€๊ฒฉ์€ ์œ ์ง€): ${result.error}`
844
- : '',
845
- result.failedStep && result.failedStep !== 'submission'
846
- ? `์˜ค๋ฅ˜: ${result.error}`
847
- : '',
848
- '',
849
- `Console: ${result.consoleUrl}`,
850
- ].filter(Boolean);
851
- return { content: [{ type: 'text', text: lines.join('\n') }] };
859
+ const creds = requireAppStoreCreds();
860
+ const result = await createAppleOneTimePurchase({
861
+ appId: args.appId,
862
+ bundleId: args.bundleId,
863
+ productId: args.productId,
864
+ name: args.name,
865
+ price: args.price,
866
+ currency: args.currency,
867
+ type: args.type,
868
+ ...(args.extraRegions && { extraRegions: args.extraRegions }),
869
+ keyId: creds.keyId,
870
+ issuerId: creds.issuerId,
871
+ privateKey: creds.privateKey,
872
+ });
873
+ if (!result.success) {
874
+ const hint = result.errorType === 'DUPLICATE'
875
+ ? '\n์ด๋ฏธ ๊ฐ™์€ productId๊ฐ€ ์กด์žฌํ•ด. App Store Connect์—์„œ ํ™•์ธํ•ด์ค˜.'
876
+ : result.errorType === 'PRICE_NOT_FOUND'
877
+ ? `\n๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฐ€๊ฒฉ: ${JSON.stringify(result.priceNearest)}`
878
+ : '';
879
+ return { content: [{ type: 'text', text: `โŒ IAP ์ƒ์„ฑ ์‹คํŒจ: ${result.error}${hint}` }] };
880
+ }
881
+ return {
882
+ content: [{
883
+ type: 'text',
884
+ text: [
885
+ `โœ“ App Store IAP ์ƒ์„ฑ ์™„๋ฃŒ`,
886
+ `productId: ${result.productId}`,
887
+ `internalId: ${result.internalId}`,
888
+ result.priceSet ? `โœ“ ๊ฐ€๊ฒฉ ์„ค์ •๋จ` : `โš  ๊ฐ€๊ฒฉ ๋ฏธ์„ค์ • (๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฐ€๊ฒฉ: ${JSON.stringify(result.priceNearest)})`,
889
+ result.extraRegionsSet?.length ? `โœ“ ์ถ”๊ฐ€ ์ง€์—ญ: ${result.extraRegionsSet.join(', ')}` : '',
890
+ '',
891
+ '์Šคํฌ๋ฆฐ์ƒทยท๋ฆฌ๋ทฐ ๋…ธํŠธ๋ฅผ ์ถ”๊ฐ€ํ•œ ํ›„ App Store Connect์—์„œ ์‹ฌ์‚ฌ ์ œ์ถœ:',
892
+ `https://appstoreconnect.apple.com/apps/${args.appId}/distribution/iaps`,
893
+ ].filter(Boolean).join('\n'),
894
+ }],
895
+ };
852
896
  });
853
897
  server.tool('appstore_create_subscription', [
854
- 'App Store์— ์ž๋™ ๊ฐฑ์‹  ๊ตฌ๋…์„ ์ƒ์„ฑ โ€” Subscription Group์ด ์—†์œผ๋ฉด ํ•จ๊ป˜ ์ƒ์„ฑ.',
855
- '๋‚ด๋ถ€ ํ๋ฆ„: subscriptionGroup find/create โ†’ POST /subscriptions(draft) โ†’ /subscriptionLocalizations โ†’ /subscriptionPrices(๋งค์นญ pricePoint) โ†’ /subscriptionSubmissions(autoSubmit=true).',
856
- 'groupReferenceName์ด ๊ฐ™์€ ๊ทธ๋ฃน ๋‚ด ๊ตฌ๋…์€ ์‚ฌ์šฉ์ž๊ฐ€ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜๋งŒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Œ (upgrade/downgrade ๊ฐ€๋Šฅ).',
857
- 'autoSubmit ์‹คํŒจ ์‹œ์—๋„ draft + ๊ฐ€๊ฒฉ๊นŒ์ง€๋Š” ์œ ์ง€ โ€” ์–ด๋””๊นŒ์ง€ ๋๋Š”์ง€ ์‘๋‹ต์œผ๋กœ ์•Œ ์ˆ˜ ์žˆ์Œ.',
898
+ 'App Store์— ์ž๋™ ๊ฐฑ์‹  ๊ตฌ๋…์„ ์ƒ์„ฑ โ€” Subscription Group ์ž๋™ ์ƒ์„ฑ ํฌํ•จ.',
899
+ '์ƒ์„ฑ ํ›„ App Store Connect์—์„œ ์Šคํฌ๋ฆฐ์ƒทยท๋ฆฌ๋ทฐ ๋…ธํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ์‹ฌ์‚ฌ ์ œ์ถœ ๊ฐ€๋Šฅ.',
858
900
  ].join(' '), {
859
901
  appId: z.string().describe('App Store ์•ฑ ID'),
860
- groupReferenceName: z
861
- .string()
862
- .describe("๊ตฌ๋… ๊ทธ๋ฃน ๋‚ด๋ถ€ ์ด๋ฆ„ (์˜ˆ: 'premium'). ์—†์œผ๋ฉด ์ž๋™ ์ƒ์„ฑ, ์žˆ์œผ๋ฉด ๊ธฐ์กด ๊ทธ๋ฃน ์žฌ์‚ฌ์šฉ."),
863
902
  productId: z.string().describe('๊ตฌ๋… productId (์˜ˆ: com.example.premium.monthly)'),
864
- referenceName: z.string().describe('๋‚ด๋ถ€ ์‹๋ณ„ ์ด๋ฆ„'),
865
- subscriptionPeriod: z
866
- .enum(['ONE_WEEK', 'ONE_MONTH', 'TWO_MONTHS', 'THREE_MONTHS', 'SIX_MONTHS', 'ONE_YEAR'])
903
+ name: z.string().describe('๊ตฌ๋… ์ด๋ฆ„ (์Šคํ† ์–ด ๋…ธ์ถœ)'),
904
+ price: z.number().int().describe('๊ฐ€๊ฒฉ (์ตœ์†Œ ๋‹จ์œ„: USD cents. ์˜ˆ: $9.99 โ†’ 999, โ‚ฉ9,900 โ†’ 9900)'),
905
+ currency: z.string().default('USD').describe('ISO 4217 ํ†ตํ™” ์ฝ”๋“œ (๊ธฐ๋ณธ USD)'),
906
+ period: z
907
+ .enum(['monthly', 'yearly'])
867
908
  .describe('๊ตฌ๋… ์ฃผ๊ธฐ'),
868
- displayName: z.string().describe('์‚ฌ์šฉ์ž ๋…ธ์ถœ ์ด๋ฆ„'),
869
- description: z.string().describe('์‚ฌ์šฉ์ž ๋…ธ์ถœ ์„ค๋ช…'),
870
- priceUsd: z.number().describe('USD ๊ฐ€๊ฒฉ'),
871
- locale: z.string().optional().describe('๊ธฐ๋ณธ en-US'),
872
- baseTerritory: z.string().optional().describe('๊ธฐ๋ณธ USA (alpha-3)'),
873
- reviewNote: z.string().optional(),
874
- familySharable: z.boolean().optional(),
875
- groupLevel: z
876
- .number()
909
+ extraRegions: z
910
+ .array(z.object({
911
+ currency: z.string().describe('ISO 4217 ํ†ตํ™” ์ฝ”๋“œ (์˜ˆ: KRW)'),
912
+ price: z.number().describe('๊ฐ€๊ฒฉ (์ตœ์†Œ ๋‹จ์œ„)'),
913
+ }))
877
914
  .optional()
878
- .describe('๊ทธ๋ฃน ๋‚ด ํ‘œ์‹œ ์ˆœ์„œ. ๋ฏธ์ง€์ • ์‹œ Apple์ด ์ž๋™ ๋ถ€์—ฌ โ€” ๊ฐ™์€ ๊ทธ๋ฃน์— ์—ฌ๋Ÿฌ ๊ตฌ๋… ๋งŒ๋“ค ๋•Œ ๋ช…์‹œ์ ์œผ๋กœ ์ถฉ๋Œ ์•ˆ ํ•˜๋ ค๋ฉด ๋น„์›Œ๋‘˜ ๊ฒƒ.'),
879
- autoSubmit: z.boolean().optional().describe('์ž๋™ ์‹ฌ์‚ฌ ์ œ์ถœ (๊ธฐ๋ณธ true)'),
915
+ .describe('์ถ”๊ฐ€ ์ง€์—ญ๋ณ„ ๋ช…์‹œ ๊ฐ€๊ฒฉ'),
916
+ bundleId: z.string().optional().describe('๋ฒˆ๋“ค ID (appId ๋Œ€์‹  ์‚ฌ์šฉ ๊ฐ€๋Šฅ)'),
880
917
  }, async (args) => {
881
- const result = await appstore.createAutoRenewableSubscription(args);
882
- const lines = [
883
- result.failedStep
884
- ? `โš  App Store ๊ตฌ๋… ์ผ๋ถ€ ๋‹จ๊ณ„ ์‹คํŒจ (failedStep=${result.failedStep})`
885
- : `โœ“ App Store ์ž๋™ ๊ฐฑ์‹  ๊ตฌ๋… ์ƒ์„ฑ ์™„๋ฃŒ`,
886
- `subscriptionId: ${result.subscriptionId}`,
887
- `groupId: ${result.groupId} (${args.groupReferenceName})`,
888
- `productId: ${result.productId}`,
889
- result.localizationId ? `โœ“ ๋กœ์ปฌ๋ผ์ด์ œ์ด์…˜ (${args.locale ?? 'en-US'})` : 'โธ ๋กœ์ปฌ๋ผ์ด์ œ์ด์…˜ ๋ฏธ์ง„ํ–‰',
890
- result.pricesCreated
891
- ? `โœ“ ๊ฐ€๊ฒฉ ($${args.priceUsd} ${args.baseTerritory ?? 'USA'})`
892
- : 'โธ ๊ฐ€๊ฒฉ ๋ฏธ์„ค์ •',
893
- result.priceMatchWarning ? `โš  ${result.priceMatchWarning}` : '',
894
- result.submissionId
895
- ? `โœ“ ์‹ฌ์‚ฌ ์ œ์ถœ๋จ (state=${result.submissionState ?? 'WAITING_FOR_REVIEW'})`
896
- : args.autoSubmit === false
897
- ? 'โธ ์ž๋™ ์ œ์ถœ skip โ€” Console์—์„œ ์ˆ˜๋™ ์ œ์ถœ'
898
- : result.failedStep === 'submission'
899
- ? `โš  ์ž๋™ ์ œ์ถœ ์‹คํŒจ (draftยท๊ฐ€๊ฒฉ์€ ์œ ์ง€): ${result.error}`
900
- : '',
901
- result.failedStep && result.failedStep !== 'submission'
902
- ? `์˜ค๋ฅ˜: ${result.error}`
903
- : '',
904
- '',
905
- `Console: ${result.consoleUrl}`,
906
- ].filter(Boolean);
907
- return { content: [{ type: 'text', text: lines.join('\n') }] };
918
+ const creds = requireAppStoreCreds();
919
+ const result = await createAppleSubscription({
920
+ appId: args.appId,
921
+ bundleId: args.bundleId,
922
+ productId: args.productId,
923
+ name: args.name,
924
+ price: args.price,
925
+ currency: args.currency,
926
+ period: args.period,
927
+ ...(args.extraRegions && { extraRegions: args.extraRegions }),
928
+ keyId: creds.keyId,
929
+ issuerId: creds.issuerId,
930
+ privateKey: creds.privateKey,
931
+ });
932
+ if (!result.success) {
933
+ const hint = result.errorType === 'DUPLICATE'
934
+ ? '\n์ด๋ฏธ ๊ฐ™์€ productId๊ฐ€ ์กด์žฌํ•ด. App Store Connect์—์„œ ํ™•์ธํ•ด์ค˜.'
935
+ : result.errorType === 'PRICE_NOT_FOUND'
936
+ ? `\n๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฐ€๊ฒฉ: ${JSON.stringify(result.priceNearest)}`
937
+ : '';
938
+ return { content: [{ type: 'text', text: `โŒ ๊ตฌ๋… ์ƒ์„ฑ ์‹คํŒจ: ${result.error}${hint}` }] };
939
+ }
940
+ return {
941
+ content: [{
942
+ type: 'text',
943
+ text: [
944
+ `โœ“ App Store ๊ตฌ๋… ์ƒ์„ฑ ์™„๋ฃŒ`,
945
+ `productId: ${result.productId}`,
946
+ `internalId: ${result.internalId}`,
947
+ result.priceSet ? `โœ“ ๊ฐ€๊ฒฉ ์„ค์ •๋จ` : `โš  ๊ฐ€๊ฒฉ ๋ฏธ์„ค์ • (๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฐ€๊ฒฉ: ${JSON.stringify(result.priceNearest)})`,
948
+ result.extraRegionsSet?.length ? `โœ“ ์ถ”๊ฐ€ ์ง€์—ญ: ${result.extraRegionsSet.join(', ')}` : '',
949
+ result.localizationAdded ? 'โœ“ KRW ํ•œ๊ตญ์–ด ๋กœ์ปฌ๋ผ์ด์ œ์ด์…˜ ์ถ”๊ฐ€๋จ' : '',
950
+ '',
951
+ 'App Store Connect์—์„œ ์‹ฌ์‚ฌ ์ œ์ถœ:',
952
+ `https://appstoreconnect.apple.com/apps/${args.appId}/distribution/subscriptions`,
953
+ ].filter(Boolean).join('\n'),
954
+ }],
955
+ };
956
+ });
957
+ // --- SKU ์šด์˜ (์กฐํšŒ/์ˆ˜์ •/์‚ญ์ œ) โ€” onesub ์œ„์ž„ ---
958
+ server.tool('playstore_list_products', 'Google Play์˜ ๋ชจ๋“  IAP ์ƒํ’ˆ(๊ตฌ๋… + ์ผํšŒ์„ฑ) ํ†ตํ•ฉ ์กฐํšŒ. productId / name / status / type / price / currency ๋ฐ˜ํ™˜.', {
959
+ packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช…'),
960
+ }, async ({ packageName }) => {
961
+ const json = requireServiceAccountJson();
962
+ const products = await listGoogleProducts({ packageName, serviceAccountKey: json });
963
+ return { content: [{ type: 'text', text: JSON.stringify(products, null, 2) }] };
964
+ });
965
+ server.tool('playstore_update_product', 'Google Play IAP ์ƒํ’ˆ์˜ ํ‘œ์‹œ ์ด๋ฆ„ ๋ณ€๊ฒฝ (ํ˜„์žฌ name ํ•„๋“œ๋งŒ ์ˆ˜์ • ๊ฐ€๋Šฅ). productId / type / ๊ฐ€๊ฒฉ์€ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€.', {
966
+ packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช…'),
967
+ productId: z.string().describe('์ƒํ’ˆ ID'),
968
+ productType: z.enum(['subscription', 'consumable', 'non_consumable']).describe('์ƒํ’ˆ ์œ ํ˜•'),
969
+ name: z.string().describe('์ƒˆ ํ‘œ์‹œ ์ด๋ฆ„'),
970
+ }, async ({ packageName, productId, productType, name }) => {
971
+ const json = requireServiceAccountJson();
972
+ const result = await updateGoogleProduct({
973
+ packageName, productId, productType, name, serviceAccountKey: json,
974
+ });
975
+ if (!result.success) {
976
+ return { content: [{ type: 'text', text: `โŒ ์ˆ˜์ • ์‹คํŒจ: ${result.error}` }] };
977
+ }
978
+ return { content: [{ type: 'text', text: `โœ“ ์ˆ˜์ • ์™„๋ฃŒ (๋ณ€๊ฒฝ ํ•„๋“œ: ${result.updated.join(', ') || 'none'})` }] };
979
+ });
980
+ server.tool('playstore_delete_product', 'โš ๏ธ ๋น„๊ฐ€์—ญ. Google Play IAP ์ƒํ’ˆ ์‚ญ์ œ. ํ™œ์„ฑ baseplan + ๊ตฌ๋…์ž ์žˆ๋Š” ๊ตฌ๋…์€ ์‚ญ์ œ ๋ถˆ๊ฐ€.', {
981
+ packageName: z.string().describe('ํŒจํ‚ค์ง€๋ช…'),
982
+ productId: z.string().describe('์ƒํ’ˆ ID'),
983
+ productType: z.enum(['subscription', 'consumable', 'non_consumable']).describe('์ƒํ’ˆ ์œ ํ˜•'),
984
+ }, async ({ packageName, productId, productType }) => {
985
+ const json = requireServiceAccountJson();
986
+ const result = await deleteGoogleProduct({
987
+ packageName, productId, productType, serviceAccountKey: json,
988
+ });
989
+ if (!result.success) {
990
+ return { content: [{ type: 'text', text: `โŒ ์‚ญ์ œ ์‹คํŒจ: ${result.error}` }] };
991
+ }
992
+ return { content: [{ type: 'text', text: `โœ“ ${productId} ์‚ญ์ œ ์™„๋ฃŒ` }] };
993
+ });
994
+ server.tool('appstore_list_products', 'App Store์˜ ๋ชจ๋“  IAP ์ƒํ’ˆ(๊ตฌ๋… + ์ผํšŒ์„ฑ) ํ†ตํ•ฉ ์กฐํšŒ. productId / internalId / name / status / type ๋ฐ˜ํ™˜.', {
995
+ appId: z.string().describe('App Store ์•ฑ ID (์ˆซ์žํ˜•, appstore_list_apps ๊ฒฐ๊ณผ)'),
996
+ }, async ({ appId }) => {
997
+ const creds = requireAppStoreCreds();
998
+ const products = await listAppleProducts({
999
+ appId, keyId: creds.keyId, issuerId: creds.issuerId, privateKey: creds.privateKey,
1000
+ });
1001
+ return { content: [{ type: 'text', text: JSON.stringify(products, null, 2) }] };
1002
+ });
1003
+ server.tool('appstore_update_product', 'App Store IAP ์ƒํ’ˆ์˜ reference name ๋ณ€๊ฒฝ. productId / ์œ ํ˜•์€ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€.', {
1004
+ appId: z.string().optional().describe('App Store ์•ฑ ID'),
1005
+ bundleId: z.string().optional().describe('๋ฒˆ๋“ค ID (appId ๋Œ€์‹  ์‚ฌ์šฉ ๊ฐ€๋Šฅ)'),
1006
+ productId: z.string().describe('์ƒํ’ˆ ID'),
1007
+ productType: z.enum(['subscription', 'consumable', 'non_consumable']).describe('์ƒํ’ˆ ์œ ํ˜•'),
1008
+ name: z.string().describe('์ƒˆ reference name'),
1009
+ }, async ({ appId, bundleId, productId, productType, name }) => {
1010
+ const creds = requireAppStoreCreds();
1011
+ const result = await updateAppleProduct({
1012
+ appId, bundleId, productId, productType, name,
1013
+ keyId: creds.keyId, issuerId: creds.issuerId, privateKey: creds.privateKey,
1014
+ });
1015
+ if (!result.success) {
1016
+ return { content: [{ type: 'text', text: `โŒ ์ˆ˜์ • ์‹คํŒจ: ${result.error}` }] };
1017
+ }
1018
+ return { content: [{ type: 'text', text: `โœ“ ์ˆ˜์ • ์™„๋ฃŒ (๋ณ€๊ฒฝ ํ•„๋“œ: ${result.updated.join(', ') || 'none'})` }] };
1019
+ });
1020
+ server.tool('appstore_delete_product', 'โš ๏ธ ๋น„๊ฐ€์—ญ. App Store IAP ์ƒํ’ˆ ์‚ญ์ œ. MISSING_METADATA / WAITING_FOR_REVIEW ์ƒํƒœ๋งŒ ๊ฐ€๋Šฅ โ€” ์ด๋ฏธ ์Šน์ธ(READY_FOR_SALE)๋œ ์ƒํ’ˆ์€ Console์—์„œ "Remove from sale" ํ•ด์•ผ ํ•จ.', {
1021
+ appId: z.string().optional().describe('App Store ์•ฑ ID'),
1022
+ bundleId: z.string().optional().describe('๋ฒˆ๋“ค ID (appId ๋Œ€์‹  ์‚ฌ์šฉ ๊ฐ€๋Šฅ)'),
1023
+ productId: z.string().describe('์ƒํ’ˆ ID'),
1024
+ productType: z.enum(['subscription', 'consumable', 'non_consumable']).describe('์ƒํ’ˆ ์œ ํ˜•'),
1025
+ }, async ({ appId, bundleId, productId, productType }) => {
1026
+ const creds = requireAppStoreCreds();
1027
+ const result = await deleteAppleProduct({
1028
+ appId, bundleId, productId, productType,
1029
+ keyId: creds.keyId, issuerId: creds.issuerId, privateKey: creds.privateKey,
1030
+ });
1031
+ if (!result.success) {
1032
+ const hint = result.errorType === 'CANNOT_DELETE'
1033
+ ? '\n์Šน์ธ๋œ ์ƒํ’ˆ์€ API ์‚ญ์ œ ๋ถˆ๊ฐ€ โ€” App Store Connect โ†’ ์ƒํ’ˆ โ†’ "Remove from sale"'
1034
+ : '';
1035
+ return { content: [{ type: 'text', text: `โŒ ์‚ญ์ œ ์‹คํŒจ: ${result.error}${hint}` }] };
1036
+ }
1037
+ return { content: [{ type: 'text', text: `โœ“ ${productId} ์‚ญ์ œ ์™„๋ฃŒ` }] };
908
1038
  });
909
1039
  // --- App Store ์‹ฌ์‚ฌ ์ œ์ถœ (Submit for Review) ---
910
1040
  server.tool('appstore_submit_for_review', [
@@ -932,7 +1062,7 @@ server.tool('playstore_submit_release', [
932
1062
  versionCode: z.string().describe('๋Œ€์ƒ versionCode (๋ฌธ์ž์—ด)'),
933
1063
  status: z.enum(['draft', 'inProgress', 'completed', 'halted']).optional().describe('์ƒˆ status (๊ธฐ๋ณธ: completed)'),
934
1064
  }, async ({ packageName, track, versionCode, status }) => {
935
- const auth = requireAuth();
1065
+ const auth = requirePlayStoreAuth();
936
1066
  const result = await playstore.submitRelease(auth, packageName, track, versionCode, status);
937
1067
  return { content: [{ type: 'text', text: `โœ… ${packageName} ${track} v${versionCode}: ${result.previousStatus} โ†’ ${result.newStatus}\n\n${JSON.stringify(result, null, 2)}` }] };
938
1068
  });
@@ -958,7 +1088,7 @@ server.tool('playstore_promote_release', [
958
1088
  text: z.string().describe('๋ฆด๋ฆฌ์Šค ๋…ธํŠธ ๋ณธ๋ฌธ (500์ž ์ด๋‚ด)'),
959
1089
  })).optional().describe('๋Œ€์ƒ ํŠธ๋ž™์šฉ ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ (๋ฏธ์ง€์ • + copyReleaseNotes=true๋ฉด source ๊ทธ๋Œ€๋กœ)'),
960
1090
  }, async ({ packageName, fromTrack, toTrack, versionCode, status, userFraction, releaseName, copyReleaseNotes, releaseNotes }) => {
961
- const auth = requireAuth();
1091
+ const auth = requirePlayStoreAuth();
962
1092
  const result = await playstore.promoteRelease(auth, packageName, fromTrack, toTrack, versionCode, {
963
1093
  status,
964
1094
  userFraction,
@@ -1,4 +1,5 @@
1
1
  import type { OAuth2Client } from 'google-auth-library';
2
+ import { JWT } from 'google-auth-library';
2
3
  export type PlayImageType = 'featureGraphic' | 'icon' | 'phoneScreenshots' | 'promoGraphic' | 'sevenInchScreenshots' | 'tenInchScreenshots' | 'tvBanner' | 'tvScreenshots' | 'wearScreenshots';
3
4
  /**
4
5
  * Google Play Developer API (Android Publisher API v3) ๋ž˜ํผ
@@ -7,15 +8,15 @@ export type PlayImageType = 'featureGraphic' | 'icon' | 'phoneScreenshots' | 'pr
7
8
  * ์—ฌ๊ธฐ์„œ๋Š” ๊ธฐ์กด ์•ฑ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ, ๋นŒ๋“œ, ์ถœ์‹œ๋ฅผ ๊ด€๋ฆฌ.
8
9
  */
9
10
  export declare const publisher: () => import("googleapis").androidpublisher_v3.Androidpublisher;
10
- export declare function withEdit<T>(auth: OAuth2Client, packageName: string, fn: (editId: string) => Promise<T>, commit?: boolean): Promise<T>;
11
- export declare function getAppDetails(auth: OAuth2Client, packageName: string): Promise<import("googleapis").androidpublisher_v3.Schema$AppDetails>;
12
- export declare function getListing(auth: OAuth2Client, packageName: string, language?: string): Promise<import("googleapis").androidpublisher_v3.Schema$Listing>;
13
- export declare function updateListing(auth: OAuth2Client, packageName: string, language: string, data: {
11
+ export declare function withEdit<T>(auth: OAuth2Client | JWT, packageName: string, fn: (editId: string) => Promise<T>, commit?: boolean): Promise<T>;
12
+ export declare function getAppDetails(auth: OAuth2Client | JWT, packageName: string): Promise<import("googleapis").androidpublisher_v3.Schema$AppDetails>;
13
+ export declare function getListing(auth: OAuth2Client | JWT, packageName: string, language?: string): Promise<import("googleapis").androidpublisher_v3.Schema$Listing>;
14
+ export declare function updateListing(auth: OAuth2Client | JWT, packageName: string, language: string, data: {
14
15
  title?: string;
15
16
  shortDescription?: string;
16
17
  fullDescription?: string;
17
18
  }): Promise<import("googleapis").androidpublisher_v3.Schema$Listing>;
18
- export declare function listTracks(auth: OAuth2Client, packageName: string): Promise<{
19
+ export declare function listTracks(auth: OAuth2Client | JWT, packageName: string): Promise<{
19
20
  track: string | null | undefined;
20
21
  releases: {
21
22
  name: string | null | undefined;
@@ -24,20 +25,20 @@ export declare function listTracks(auth: OAuth2Client, packageName: string): Pro
24
25
  releaseNotes: import("googleapis").androidpublisher_v3.Schema$LocalizedText[] | undefined;
25
26
  }[];
26
27
  }[]>;
27
- export declare function updateReleaseNotes(auth: OAuth2Client, packageName: string, track: string, versionCode: string, language: string, text: string): Promise<import("googleapis").androidpublisher_v3.Schema$Track>;
28
+ export declare function updateReleaseNotes(auth: OAuth2Client | JWT, packageName: string, track: string, versionCode: string, language: string, text: string): Promise<import("googleapis").androidpublisher_v3.Schema$Track>;
28
29
  /**
29
30
  * ์ตœ์‹  release (versionCode ์ตœ๋Œ€๊ฐ’)์˜ releaseNotes[language]๋ฅผ ๊ต์ฒด/์ถ”๊ฐ€.
30
31
  * versionCode๋ฅผ ๋ชจ๋ฅผ ๋•Œ ํŽธ์˜์šฉ.
31
32
  */
32
- export declare function updateLatestReleaseNotes(auth: OAuth2Client, packageName: string, track: string, language: string, text: string): Promise<{
33
+ export declare function updateLatestReleaseNotes(auth: OAuth2Client | JWT, packageName: string, track: string, language: string, text: string): Promise<{
33
34
  updatedVersionCodes: string[] | null | undefined;
34
35
  updatedReleaseName: string | null | undefined;
35
36
  releases?: import("googleapis").androidpublisher_v3.Schema$TrackRelease[];
36
37
  track?: string | null;
37
38
  }>;
38
- export declare function listImages(auth: OAuth2Client, packageName: string, language: string, imageType: PlayImageType): Promise<import("googleapis").androidpublisher_v3.Schema$Image[]>;
39
- export declare function uploadImage(auth: OAuth2Client, packageName: string, language: string, imageType: PlayImageType, filePath: string): Promise<import("googleapis").androidpublisher_v3.Schema$Image | undefined>;
40
- export declare function deleteAllImages(auth: OAuth2Client, packageName: string, language: string, imageType: PlayImageType): Promise<{
39
+ export declare function listImages(auth: OAuth2Client | JWT, packageName: string, language: string, imageType: PlayImageType): Promise<import("googleapis").androidpublisher_v3.Schema$Image[]>;
40
+ export declare function uploadImage(auth: OAuth2Client | JWT, packageName: string, language: string, imageType: PlayImageType, filePath: string): Promise<import("googleapis").androidpublisher_v3.Schema$Image | undefined>;
41
+ export declare function deleteAllImages(auth: OAuth2Client | JWT, packageName: string, language: string, imageType: PlayImageType): Promise<{
41
42
  ok: boolean;
42
43
  imageType: PlayImageType;
43
44
  }>;
@@ -45,7 +46,7 @@ export declare function deleteAllImages(auth: OAuth2Client, packageName: string,
45
46
  * ํ•œ edit ์„ธ์…˜ ๋‚ด์—์„œ ๊ธฐ์กด ์ด๋ฏธ์ง€ ์ „์ฒด ์‚ญ์ œ + ์ƒˆ ์ด๋ฏธ์ง€ ์ˆœ์„œ๋Œ€๋กœ ์—…๋กœ๋“œ + commit.
46
47
  * phoneScreenshots์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ์žฅ ๊ต์ฒด ์‹œ ํšจ์œจ์  (๋‹จ์ผ edit).
47
48
  */
48
- export declare function replaceImages(auth: OAuth2Client, packageName: string, language: string, imageType: PlayImageType, filePaths: string[]): Promise<{
49
+ export declare function replaceImages(auth: OAuth2Client | JWT, packageName: string, language: string, imageType: PlayImageType, filePaths: string[]): Promise<{
49
50
  imageType: PlayImageType;
50
51
  count: number;
51
52
  uploaded: {
@@ -54,7 +55,7 @@ export declare function replaceImages(auth: OAuth2Client, packageName: string, l
54
55
  sha256?: string | null;
55
56
  }[];
56
57
  }>;
57
- export declare function submitRelease(auth: OAuth2Client, packageName: string, track: string, versionCode: string, status?: 'completed' | 'draft' | 'inProgress' | 'halted'): Promise<{
58
+ export declare function submitRelease(auth: OAuth2Client | JWT, packageName: string, track: string, versionCode: string, status?: 'completed' | 'draft' | 'inProgress' | 'halted'): Promise<{
58
59
  track: string;
59
60
  versionCode: string;
60
61
  previousStatus: string | null | undefined;
@@ -72,7 +73,7 @@ export interface PromoteReleaseOptions {
72
73
  }>;
73
74
  copyReleaseNotes?: boolean;
74
75
  }
75
- export declare function promoteRelease(auth: OAuth2Client, packageName: string, fromTrack: string, toTrack: string, versionCode: string, options?: PromoteReleaseOptions): Promise<{
76
+ export declare function promoteRelease(auth: OAuth2Client | JWT, packageName: string, fromTrack: string, toTrack: string, versionCode: string, options?: PromoteReleaseOptions): Promise<{
76
77
  packageName: string;
77
78
  fromTrack: string;
78
79
  toTrack: string;
@@ -84,7 +85,7 @@ export declare function promoteRelease(auth: OAuth2Client, packageName: string,
84
85
  committed: boolean;
85
86
  result: import("googleapis").androidpublisher_v3.Schema$Track;
86
87
  }>;
87
- export declare function listReviews(auth: OAuth2Client, packageName: string): Promise<{
88
+ export declare function listReviews(auth: OAuth2Client | JWT, packageName: string): Promise<{
88
89
  reviewId: string | null | undefined;
89
90
  authorName: string | null | undefined;
90
91
  comments: {
@@ -94,59 +95,17 @@ export declare function listReviews(auth: OAuth2Client, packageName: string): Pr
94
95
  deviceMetadata: string | null | undefined;
95
96
  }[] | undefined;
96
97
  }[]>;
97
- export declare function replyToReview(auth: OAuth2Client, packageName: string, reviewId: string, replyText: string): Promise<import("googleapis").androidpublisher_v3.Schema$ReviewsReplyResponse>;
98
- export declare function listInAppProducts(auth: OAuth2Client, packageName: string): Promise<{
98
+ export declare function replyToReview(auth: OAuth2Client | JWT, packageName: string, reviewId: string, replyText: string): Promise<import("googleapis").androidpublisher_v3.Schema$ReviewsReplyResponse>;
99
+ export declare function listInAppProducts(auth: OAuth2Client | JWT, packageName: string): Promise<{
99
100
  productId: any;
100
101
  listings: any;
101
102
  purchaseOptions: any;
102
103
  }[]>;
103
- export declare function listSubscriptions(auth: OAuth2Client, packageName: string): Promise<{
104
+ export declare function listSubscriptions(auth: OAuth2Client | JWT, packageName: string): Promise<{
104
105
  productId: string | null | undefined;
105
106
  basePlans: import("googleapis").androidpublisher_v3.Schema$BasePlan[] | undefined;
106
107
  listings: import("googleapis").androidpublisher_v3.Schema$SubscriptionListing[] | undefined;
107
108
  }[]>;
108
- export interface CreateOneTimeProductInput {
109
- packageName: string;
110
- productId: string;
111
- title: string;
112
- description: string;
113
- languageCode?: string;
114
- priceUsd: number;
115
- purchaseOptionId?: string;
116
- legacyCompatible?: boolean;
117
- regionsVersion?: string;
118
- newRegionsPricing?: {
119
- usdPrice: number;
120
- eurPrice: number;
121
- };
122
- }
123
- export declare function createOnetimeProduct(auth: OAuth2Client, input: CreateOneTimeProductInput): Promise<import("googleapis").androidpublisher_v3.Schema$OneTimeProduct>;
124
- export interface CreateSubscriptionInput {
125
- packageName: string;
126
- productId: string;
127
- title: string;
128
- description: string;
129
- benefits?: string[];
130
- languageCode?: string;
131
- basePlanId?: string;
132
- billingPeriod: 'P1W' | 'P1M' | 'P3M' | 'P6M' | 'P1Y';
133
- priceUsd: number;
134
- gracePeriod?: 'P0D' | 'P3D' | 'P7D' | 'P14D' | 'P30D';
135
- resubscribeState?: 'RESUBSCRIBE_STATE_ACTIVE' | 'RESUBSCRIBE_STATE_INACTIVE';
136
- autoActivate?: boolean;
137
- regionsVersion?: string;
138
- newRegionsPricing?: {
139
- usdPrice: number;
140
- eurPrice: number;
141
- };
142
- }
143
- export declare function createSubscription(auth: OAuth2Client, input: CreateSubscriptionInput): Promise<{
144
- productId: string;
145
- basePlanId: string;
146
- basePlanState?: string | null;
147
- activated: boolean;
148
- activateError?: string;
149
- }>;
150
109
  export type ServiceAccountVerifyResult = {
151
110
  ok: true;
152
111
  clientEmail: string;
@@ -384,166 +384,6 @@ export async function listSubscriptions(auth, packageName) {
384
384
  listings: s.listings,
385
385
  }));
386
386
  }
387
- function moneyFromNumber(amount, currencyCode) {
388
- const units = Math.trunc(amount).toString();
389
- const nanos = Math.round((amount - Math.trunc(amount)) * 1_000_000_000);
390
- return { currencyCode, units, nanos };
391
- }
392
- export async function createOnetimeProduct(auth, input) {
393
- const languageCode = input.languageCode ?? 'en-US';
394
- const purchaseOptionId = input.purchaseOptionId ?? 'default-buy';
395
- const regionsVersion = input.regionsVersion ?? '2022/02';
396
- const usdPrice = moneyFromNumber(input.priceUsd, 'USD');
397
- const newRegionsConfig = input.newRegionsPricing
398
- ? {
399
- availability: 'AVAILABLE',
400
- usdPrice: moneyFromNumber(input.newRegionsPricing.usdPrice, 'USD'),
401
- eurPrice: moneyFromNumber(input.newRegionsPricing.eurPrice, 'EUR'),
402
- }
403
- : { availability: 'UNAVAILABLE' };
404
- const res = await publisher().monetization.onetimeproducts.patch({
405
- auth,
406
- packageName: input.packageName,
407
- productId: input.productId,
408
- allowMissing: true,
409
- updateMask: 'listings,purchaseOptions',
410
- 'regionsVersion.version': regionsVersion,
411
- requestBody: {
412
- packageName: input.packageName,
413
- productId: input.productId,
414
- listings: [
415
- {
416
- languageCode,
417
- title: input.title,
418
- description: input.description,
419
- },
420
- ],
421
- purchaseOptions: [
422
- {
423
- purchaseOptionId,
424
- buyOption: {
425
- legacyCompatible: input.legacyCompatible ?? true,
426
- },
427
- regionalPricingAndAvailabilityConfigs: [
428
- {
429
- regionCode: 'US',
430
- availability: 'AVAILABLE',
431
- price: usdPrice,
432
- },
433
- ],
434
- newRegionsConfig,
435
- },
436
- ],
437
- },
438
- });
439
- return res.data;
440
- }
441
- function defaultBasePlanIdFor(period) {
442
- switch (period) {
443
- case 'P1W': return 'weekly';
444
- case 'P1M': return 'monthly';
445
- case 'P3M': return 'quarterly';
446
- case 'P6M': return 'semi-yearly';
447
- case 'P1Y': return 'yearly';
448
- default: return 'default';
449
- }
450
- }
451
- export async function createSubscription(auth, input) {
452
- const languageCode = input.languageCode ?? 'en-US';
453
- const basePlanId = input.basePlanId ?? defaultBasePlanIdFor(input.billingPeriod);
454
- const regionsVersion = input.regionsVersion ?? '2022/02';
455
- const usdPrice = moneyFromNumber(input.priceUsd, 'USD');
456
- const otherRegionsConfig = input.newRegionsPricing
457
- ? {
458
- newSubscriberAvailability: true,
459
- usdPrice: moneyFromNumber(input.newRegionsPricing.usdPrice, 'USD'),
460
- eurPrice: moneyFromNumber(input.newRegionsPricing.eurPrice, 'EUR'),
461
- }
462
- : { newSubscriberAvailability: false };
463
- // 1) subscription ์…ธ upsert (allowMissing=true๋กœ idempotent)
464
- // updateMask๋Š” mutable ํ•„๋“œ๋งŒ โ€” packageName/productId๋Š” immutable์ด๋ผ ํฌํ•จ ์‹œ reject ๊ฐ€๋Šฅ.
465
- await publisher().monetization.subscriptions.patch({
466
- auth,
467
- packageName: input.packageName,
468
- productId: input.productId,
469
- allowMissing: true,
470
- updateMask: 'listings',
471
- 'regionsVersion.version': regionsVersion,
472
- requestBody: {
473
- packageName: input.packageName,
474
- productId: input.productId,
475
- listings: [
476
- {
477
- languageCode,
478
- title: input.title,
479
- description: input.description,
480
- benefits: input.benefits,
481
- },
482
- ],
483
- },
484
- });
485
- // 2) basePlan ์ƒ์„ฑ (์ด๋ฏธ ์žˆ์œผ๋ฉด conflict โ€” ๊ทธ๋• update ์‹œ๋„)
486
- let basePlanState;
487
- try {
488
- const created = await publisher().monetization.subscriptions.basePlans.create({
489
- auth,
490
- packageName: input.packageName,
491
- productId: input.productId,
492
- basePlanId,
493
- 'regionsVersion.version': regionsVersion,
494
- requestBody: {
495
- basePlanId,
496
- autoRenewingBasePlanType: {
497
- billingPeriodDuration: input.billingPeriod,
498
- gracePeriodDuration: input.gracePeriod ?? 'P7D',
499
- resubscribeState: input.resubscribeState ?? 'RESUBSCRIBE_STATE_ACTIVE',
500
- },
501
- regionalConfigs: [
502
- {
503
- regionCode: 'US',
504
- newSubscriberAvailability: true,
505
- price: usdPrice,
506
- },
507
- ],
508
- otherRegionsConfig,
509
- },
510
- });
511
- basePlanState = created.data?.state;
512
- }
513
- catch (err) {
514
- // ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ 409 โ€” ๊ทธ๋ƒฅ ์ง„ํ–‰
515
- const e = err;
516
- if (e.code !== 409 && e.status !== 409) {
517
- throw err;
518
- }
519
- }
520
- // 3) ์ž๋™ ํ™œ์„ฑํ™” (์˜ต์…˜)
521
- let activated = false;
522
- let activateError;
523
- if (input.autoActivate !== false) {
524
- try {
525
- const activated_ = await publisher().monetization.subscriptions.basePlans.activate({
526
- auth,
527
- packageName: input.packageName,
528
- productId: input.productId,
529
- basePlanId,
530
- requestBody: {},
531
- });
532
- activated = true;
533
- basePlanState = (activated_.data?.basePlans?.find?.((b) => b.basePlanId === basePlanId)?.state) ?? basePlanState;
534
- }
535
- catch (err) {
536
- activateError = err.message;
537
- }
538
- }
539
- return {
540
- productId: input.productId,
541
- basePlanId,
542
- basePlanState,
543
- activated,
544
- activateError,
545
- };
546
- }
547
387
  export async function verifyServiceAccountJson(serviceAccountJson, packageName) {
548
388
  let parsed;
549
389
  try {
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "Mimi Seed MCP server โ€” Firebase + AdMob + Google Play + App Store management for Claude Code / Cursor / any MCP client.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mimi-seed-mcp": "dist/index.js",
8
8
  "mimi-seed-auth": "dist/auth/cli.js",
9
- "mimi-seed-appstore-auth": "dist/appstore/setup-cli.js"
9
+ "mimi-seed-appstore-auth": "dist/appstore/setup-cli.js",
10
+ "mimi-seed-playstore-auth": "dist/auth/playstore-setup-cli.js"
10
11
  },
11
12
  "files": [
12
13
  "dist"
@@ -44,6 +45,7 @@
44
45
  "dependencies": {
45
46
  "@anthropic-ai/sdk": "^0.52.0",
46
47
  "@modelcontextprotocol/sdk": "^1.12.1",
48
+ "@onesub/providers": "^0.2.0",
47
49
  "googleapis": "^171.4.0",
48
50
  "jsonwebtoken": "^9.0.3",
49
51
  "open": "^10.1.0",