@yoonion/mimi-seed-mcp 0.13.13 → 0.13.15

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/README.md CHANGED
@@ -78,8 +78,8 @@ export ANTHROPIC_API_KEY=sk-ant-...
78
78
 
79
79
  | 영역 | 도구 수 | 주요 도구 |
80
80
  |------|---------|-----------|
81
- | App Store Connect | 34 | `appstore_submit_for_review` / `appstore_upload_screenshot` / `appstore_update_product_review_note` / `appstore_upload_product_review_screenshot` |
82
- | Google Play | 29 | `playstore_submit_release` / `playstore_promote_release` / `playstore_replace_images` / `playstore_reply_review` / `playstore_verify_service_account` |
81
+ | App Store Connect | 41 | `appstore_submit_for_review` / `appstore_upload_screenshot` / `appstore_update_product_review_note` / `appstore_upload_product_review_screenshot` |
82
+ | Google Play | 32 | `playstore_submit_release` / `playstore_promote_release` / `playstore_replace_images` / `playstore_reply_review` / `playstore_verify_service_account` |
83
83
  | Firebase | 20 | `firebase_create_project` / `firebase_create_android_app` / `firebase_get_android_config` / `firebase_create_ios_app` |
84
84
  | AdMob | 7 | `admob_list_apps` / `admob_create_ad_unit` / `admob_get_today_earnings` / `admob_get_report` |
85
85
  | CI/CD (GitHub Actions · GitLab) | 6 | `ci_trigger_build` / `ci_get_build_status` / `ci_list_workflows` / `ci_cancel_build` |
@@ -16,7 +16,7 @@ and which actions are irreversible.
16
16
 
17
17
  ## 0. The one thing that trips every agent: deferred tools
18
18
 
19
- Mimi Seed exposes **150+ MCP tools** across 19 domains (full inventory:
19
+ Mimi Seed exposes **150+ MCP tools** across every store, cloud, CI, social, and video domain (full inventory:
20
20
  [`docs/domain/tool-catalog.md`](domain/tool-catalog.md)). Harnesses that lazy-load large tool catalogs —
21
21
  **Claude Code most notably** — register these tools as **deferred**: the tool *names*
22
22
  are visible (in a system reminder), but the **input schemas are not loaded**. If you
@@ -42,20 +42,36 @@ tools directly — but the *call order* and *safety rules* below still apply.
42
42
 
43
43
  ### Ready-made `select:` batches
44
44
 
45
+ Every registered tool appears in at least one batch below (test-enforced), so a task you can name is a batch
46
+ you can paste. Pick the row for the job; batching two rows in one `select:` call is fine.
47
+
45
48
  | Goal | `ToolSearch` query |
46
49
  |------|--------------------|
47
50
  | First contact / "what's connected?" | `select:mimi_seed_status,mimi_seed_auth_status,mimi_seed_auth_start,mimi_seed_remote_sync_credentials` |
48
- | Play Store release | `select:playstore_get_app,playstore_list_tracks,playstore_update_latest_release_notes,playstore_promote_release,playstore_submit_release,playstore_check_submission_risks,playstore_plan_release` |
49
- | Play Store listing + images | `select:playstore_get_listing,playstore_update_listing,playstore_upload_image,playstore_list_images,playstore_replace_images,playstore_delete_all_images` |
50
- | App Store / TestFlight | `select:appstore_list_apps,appstore_list_versions,appstore_create_version,appstore_get_metadata,appstore_update_whats_new,appstore_list_builds,appstore_attach_latest_build,appstore_submit_for_review,appstore_check_submission_risks,appstore_plan_release` |
51
- | App Store screenshots | `select:appstore_list_app_info_localizations,appstore_get_metadata,appstore_list_screenshots,appstore_upload_screenshot,appstore_delete_screenshot_set,screenshot_validate` |
52
- | App Store IAP review metadata | `select:appstore_list_apps,appstore_list_products,appstore_update_product_review_note,appstore_upload_product_review_screenshot` |
51
+ | Release readiness (either store) | `select:release_status,playstore_check_submission_risks,appstore_check_submission_risks,screenshot_validate` |
52
+ | Play Store release | `select:playstore_get_app,playstore_list_tracks,playstore_update_release_notes,playstore_update_latest_release_notes,playstore_promote_release,playstore_submit_release,playstore_check_submission_risks,playstore_plan_release` |
53
+ | Play Store listing + images | `select:playstore_get_listing,playstore_update_listing,playstore_update_details,playstore_upload_image,playstore_list_images,playstore_replace_images,playstore_delete_all_images` |
54
+ | Play Store reviews + stats | `select:playstore_list_reviews,playstore_reply_review,playstore_get_statistics,generate_review_reply` |
55
+ | Play Store IAP / subscriptions | `select:playstore_list_products,playstore_list_inapp_products,playstore_list_subscriptions,playstore_create_onetime_product,playstore_create_subscription,playstore_update_product,playstore_update_product_listing,playstore_update_subscription_listing,playstore_update_product_state,playstore_delete_product` |
56
+ | App Store / TestFlight | `select:appstore_list_apps,appstore_verify_credentials,appstore_get_app,appstore_list_versions,appstore_create_version,appstore_get_metadata,appstore_update_whats_new,appstore_list_builds,appstore_attach_build,appstore_attach_latest_build,appstore_list_beta_groups,appstore_submit_for_review,appstore_check_submission_risks,appstore_plan_release` |
57
+ | App Store review submission (the bundle) | `select:appstore_list_review_submissions,appstore_add_version_to_review_submission,appstore_remove_review_submission_item,appstore_update_version_string,appstore_cancel_review` |
58
+ | App Store screenshots | `select:appstore_list_app_info_localizations,appstore_get_metadata,appstore_list_screenshots,appstore_upload_screenshot,appstore_delete_screenshot,appstore_delete_screenshot_set,screenshot_validate` |
59
+ | App Store app info + review notes | `select:appstore_get_app_info,appstore_update_app_info_localization,appstore_create_app_info_localization,appstore_update_localization,appstore_get_review_notes,appstore_update_review_notes` |
60
+ | App Store reviews | `select:appstore_list_reviews,appstore_reply_review,generate_review_reply` |
61
+ | App Store IAP (products + review metadata) | `select:appstore_list_products,appstore_create_inapp_purchase,appstore_create_subscription,appstore_update_product,appstore_list_product_localizations,appstore_update_product_localization,appstore_update_product_review_note,appstore_upload_product_review_screenshot,appstore_add_product_to_review,appstore_delete_product` |
53
62
  | Release notes from commits | `select:generate_release_notes_from_commits,playstore_update_release_notes,appstore_update_whats_new` |
54
- | Firebase setup | `select:firebase_list_projects,firebase_get_project,firebase_create_project,firebase_create_android_app,firebase_create_ios_app,firebase_get_android_config,firebase_enable_common_services` |
55
- | AdMob | `select:admob_list_accounts,admob_list_apps,admob_create_ad_unit,admob_list_ad_units,admob_get_today_earnings,admob_get_report` |
56
- | Jenkins credentials + jobs | `select:jenkins_status,jenkins_save_config,jenkins_list_credentials,jenkins_create_credential,jenkins_upload_keystore,jenkins_upload_playstore_sa,jenkins_list_jobs,jenkins_get_job_config,jenkins_create_job,jenkins_update_job` |
57
- | CI (GitHub/GitLab) | `select:ci_save_config,ci_list_workflows,ci_trigger_build,ci_get_build_status,ci_list_recent_builds` |
58
- | Service account end-to-end | `select:iam_create_service_account,iam_create_key,iam_add_iam_policy_binding,playstore_register_service_account,playstore_verify_service_account` |
63
+ | Firebase setup | `select:firebase_list_projects,firebase_get_project,firebase_create_project,firebase_create_android_app,firebase_create_ios_app,firebase_get_android_config,firebase_get_ios_config,firebase_enable_common_services` |
64
+ | Firebase apps + services (incl. web) | `select:firebase_list_android_apps,firebase_list_ios_apps,firebase_list_web_apps,firebase_create_web_app,firebase_get_web_config,firebase_enable_service,firebase_list_enabled_services,firebase_delete_android_app,firebase_delete_ios_app,firebase_delete_web_app` |
65
+ | Analytics wiring (Firebase GA4 ↔ BigQuery) | `select:firebase_link_analytics,firebase_get_analytics_details,ga4_list_account_summaries,ga4_list_properties,ga4_create_property,ga4_list_data_streams,ga4_create_data_stream,ga4_plan_bigquery_link,ga4_create_bigquery_link,ga4_run_report` |
66
+ | BigQuery | `select:bigquery_auth_status,bigquery_list_datasets,bigquery_list_tables,bigquery_get_table_schema,bigquery_run_query` |
67
+ | AdMob | `select:admob_list_accounts,admob_list_apps,admob_create_app,admob_create_ad_unit,admob_list_ad_units,admob_get_today_earnings,admob_get_report` |
68
+ | Google Ads (UAC) | `select:googleads_config_status,googleads_save_config,googleads_list_accessible_customers,googleads_list_campaigns,googleads_get_campaign_report,googleads_get_uac_report` |
69
+ | Search Console | `select:gsc_list_sites,gsc_list_sitemaps,gsc_get_sitemap,gsc_submit_sitemap,gsc_inspect_url,gsc_search_analytics` |
70
+ | Social posting (Facebook / Instagram / Threads) | `select:facebook_current_config,facebook_save_config,facebook_list_pages,facebook_get_page,facebook_post_photo,facebook_post_multi_photo,instagram_save_config,instagram_get_account,instagram_post_image,instagram_post_carousel,threads_current_config,threads_save_config,threads_refresh_token,threads_get_account,threads_post,threads_post_carousel` |
71
+ | Jenkins credentials + jobs | `select:jenkins_status,jenkins_save_config,jenkins_list_credentials,jenkins_create_credential,jenkins_delete_credential,jenkins_upload_keystore,jenkins_upload_playstore_sa,jenkins_list_jobs,jenkins_get_job_config,jenkins_create_job,jenkins_update_job` |
72
+ | CI (GitHub/GitLab) | `select:ci_save_config,ci_list_workflows,ci_trigger_build,ci_get_build_status,ci_list_recent_builds,ci_cancel_build` |
73
+ | Android signing / keystore | `select:android_signing_setup,android_generate_keystore,jenkins_upload_keystore,jenkins_upload_playstore_sa` |
74
+ | Service account end-to-end | `select:iam_list_service_accounts,iam_create_service_account,iam_list_keys,iam_create_key,iam_add_iam_policy_binding,setup_playstore_connection,playstore_register_service_account,playstore_verify_service_account,playstore_list_service_accounts,playstore_delete_service_account` |
59
75
  | Story → researched video | `select:video_plan_from_story,video_research_youtube,video_search_stock_assets,video_synthesize_research,video_download_stock_assets,video_generate_image,video_add_local_asset,video_build_timeline,video_render,video_job_status,video_validate` |
60
76
  | YouTube upload / publish | `select:youtube_upload_video,youtube_get_video_status,youtube_update_video_privacy,mimi_seed_auth_start` |
61
77
 
@@ -156,7 +172,10 @@ per-domain inventory is [`docs/domain/tool-catalog.md`](domain/tool-catalog.md).
156
172
  | **Jenkins** (credentials + jobs) | `jenkins_status` · `jenkins_save_config` · `jenkins_create_credential` · `jenkins_upload_keystore` · `jenkins_upload_playstore_sa` · `jenkins_create_job` · `jenkins_update_job` |
157
173
  | **Google Cloud IAM** | `iam_create_service_account` · `iam_create_key` · `iam_add_iam_policy_binding` |
158
174
  | **BigQuery** | `bigquery_run_query` · `bigquery_list_datasets` · `bigquery_get_table_schema` |
175
+ | **GA4** | `ga4_list_properties` · `ga4_create_property` · `ga4_create_data_stream` · `ga4_run_report` · `ga4_plan_bigquery_link` · `ga4_create_bigquery_link` |
176
+ | **Google Ads (UAC)** | `googleads_list_campaigns` · `googleads_get_uac_report` · `googleads_get_campaign_report` · `googleads_config_status` |
159
177
  | **Search Console** | `gsc_inspect_url` · `gsc_search_analytics` · `gsc_submit_sitemap` |
178
+ | **Android signing** | `android_signing_setup` · `android_generate_keystore` · `jenkins_upload_playstore_sa` |
160
179
  | **Facebook / Instagram / Threads** | `facebook_post_photo` · `instagram_post_carousel` · `threads_post` · `threads_refresh_token` |
161
180
  | **Checks** | `playstore_check_submission_risks` · `appstore_check_submission_risks` · `screenshot_validate` · `release_status` |
162
181
  | **AI / Auth** | `generate_release_notes_from_commits` · `generate_review_reply` · `mimi_seed_status` · `mimi_seed_auth_start` · `mimi_seed_auth_status` · `mimi_seed_remote_sync_credentials` |
@@ -183,6 +202,10 @@ per-domain inventory is [`docs/domain/tool-catalog.md`](domain/tool-catalog.md).
183
202
  `playstore_update_release_notes` / `appstore_update_whats_new`.
184
203
 
185
204
  ### Story → researched video
205
+ For visual production, use the bundled `video-create-publish` skill. It requires an explicit typography system,
206
+ aspect-ratio-specific human-safe crops, video-native motion, an original-resolution frame review, and a saved
207
+ contact sheet; codec validation alone is not a quality pass.
208
+
186
209
  1. `video_plan_from_story` — create the project and scene plan.
187
210
  2. `video_research_youtube` — collect reference-only metadata; never treat a result as a render asset.
188
211
  3. `video_search_stock_assets` — find licensed Pexels candidates.
@@ -12,12 +12,15 @@ export declare function updateProductReviewNote(args: {
12
12
  }>;
13
13
  /**
14
14
  * 기존 IAP/구독 상품에 App Review 스크린샷을 reserve → upload → commit 한다.
15
- * App Store Connect API는 상품당 심사용 스크린샷 하나를 허용한다.
15
+ * App Store Connect API는 상품당 심사용 스크린샷 하나를 허용한다 —
16
+ * 이미 있으면 409 `MEDIA_ASSET_CREATION_NOT_ALLOWED: Screenshot already exists` 다.
17
+ * 갈아끼우려면 replace: true (기존 것을 지우고 올린다).
16
18
  */
17
19
  export declare function uploadProductReviewScreenshot(args: {
18
20
  internalId: string;
19
21
  productType: AppStoreProductType;
20
22
  filePath: string;
23
+ replace?: boolean;
21
24
  }): Promise<{
22
25
  id: string;
23
26
  internalId: string;
@@ -26,4 +29,5 @@ export declare function uploadProductReviewScreenshot(args: {
26
29
  fileSize: number;
27
30
  state?: string;
28
31
  verified: boolean;
32
+ replacedId?: string;
29
33
  }>;
@@ -70,12 +70,31 @@ async function uploadChunks(buffer, operations) {
70
70
  }
71
71
  }
72
72
  }
73
+ /**
74
+ * 상품에 이미 붙어 있는 심사 스크린샷 id 를 찾는다. 없으면 null.
75
+ *
76
+ * 조회 경로가 상품 종류마다 다르다 — 구독은 v1, IAP 는 **v2** 다.
77
+ * v1 으로 IAP 를 조회하면 스크린샷이 있어도 404 가 나서 "없음"으로 오독하기 쉽다
78
+ * (2026-07-25 실측: 404 를 믿고 업로드했다가 "Screenshot already exists" 409).
79
+ */
80
+ async function findExistingReviewScreenshotId(internalId, productType, authHeaders) {
81
+ const product = productResource(productType);
82
+ try {
83
+ const res = await apiRequest(product.base, `${product.path}/${internalId}/appStoreReviewScreenshot`, authHeaders, { method: 'GET' });
84
+ return res?.data?.id ?? null;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
73
90
  /**
74
91
  * 기존 IAP/구독 상품에 App Review 스크린샷을 reserve → upload → commit 한다.
75
- * App Store Connect API는 상품당 심사용 스크린샷 하나를 허용한다.
92
+ * App Store Connect API는 상품당 심사용 스크린샷 하나를 허용한다 —
93
+ * 이미 있으면 409 `MEDIA_ASSET_CREATION_NOT_ALLOWED: Screenshot already exists` 다.
94
+ * 갈아끼우려면 replace: true (기존 것을 지우고 올린다).
76
95
  */
77
96
  export async function uploadProductReviewScreenshot(args) {
78
- const { internalId, productType, filePath } = args;
97
+ const { internalId, productType, filePath, replace = false } = args;
79
98
  if (!path.isAbsolute(filePath)) {
80
99
  throw new Error(`절대 경로가 필요해: ${filePath}`);
81
100
  }
@@ -94,6 +113,15 @@ export async function uploadProductReviewScreenshot(args) {
94
113
  const checksum = crypto.createHash('md5').update(buffer).digest('hex');
95
114
  const resource = reviewScreenshotResource(productType);
96
115
  const authHeaders = await authHeadersOrThrow();
116
+ // 상품당 1장이라 새로 올리려면 기존 것을 먼저 지워야 한다.
117
+ let replacedId;
118
+ if (replace) {
119
+ const existing = await findExistingReviewScreenshotId(internalId, productType, authHeaders);
120
+ if (existing) {
121
+ await apiRequest(V1_BASE, `${resource.path}/${existing}`, authHeaders, { method: 'DELETE' });
122
+ replacedId = existing;
123
+ }
124
+ }
97
125
  const reserved = await apiRequest(V1_BASE, resource.path, authHeaders, {
98
126
  method: 'POST',
99
127
  headers: { 'Content-Type': 'application/json' },
@@ -148,5 +176,5 @@ export async function uploadProductReviewScreenshot(args) {
148
176
  catch {
149
177
  // commit 성공 후 확인 GET만 실패하면 재업로드를 유도하지 않는다.
150
178
  }
151
- return { id: screenshotId, internalId, productType, fileName, fileSize, state, verified };
179
+ return { id: screenshotId, internalId, productType, fileName, fileSize, state, verified, replacedId };
152
180
  }
@@ -40,6 +40,23 @@ export declare function createVersion(input: CreateVersionInput): Promise<{
40
40
  releaseType: any;
41
41
  createdDate: any;
42
42
  }>;
43
+ /**
44
+ * 기존 버전 레코드의 versionString 을 바꾼다 (예: 2.0.5 → 2.0.6).
45
+ *
46
+ * 왜 필요한가: ASC 는 편집 가능한 버전이 이미 있으면 새 버전 생성을 거부한다
47
+ * 409 ENTITY_ERROR.RELATIONSHIP.INVALID "You cannot create a new version of the App in the current state"
48
+ * 그래서 거절/철회된 버전으로 다음 릴리스를 내보내려면 **같은 레코드의 이름을 올려야** 한다.
49
+ * 빌드는 CFBundleShortVersionString 이 일치하는 버전에만 붙으므로, 2.0.6 빌드를 올렸다면
50
+ * 버전 레코드도 2.0.6 이어야 attach 가 된다.
51
+ *
52
+ * 편집 가능한 상태(PREPARE_FOR_SUBMISSION / DEVELOPER_REJECTED / REJECTED 등)에서만 통한다.
53
+ */
54
+ export declare function updateVersionString(versionId: string, versionString: string): Promise<{
55
+ versionId: string;
56
+ versionString: any;
57
+ state: any;
58
+ platform: any;
59
+ }>;
43
60
  export declare function attachBuildToVersion(versionId: string, buildId: string): Promise<{
44
61
  versionId: string;
45
62
  buildId: string;
@@ -212,6 +229,30 @@ export declare function removeReviewSubmissionItem(itemId: string): Promise<{
212
229
  state?: string;
213
230
  removed: boolean;
214
231
  }>;
232
+ /**
233
+ * 이미 존재하는 묶음에 앱 버전을 항목으로 끼워 넣는다.
234
+ *
235
+ * 앱 첫 심사에서 반드시 필요하다. 웹에서 상품을 담으면 **상품만 든 묶음**이 새로 생기는데,
236
+ * 그대로 제출하면 Apple 이 막는다:
237
+ * 409 ENTITY_ERROR.RELATIONSHIP.REQUIRED
238
+ * "must have an approved appStoreVersions ... or an appStoreVersions must be included
239
+ * in this review submission"
240
+ * 즉 상품 묶음에는 앱 버전이 함께 있어야 한다. reviewSubmissionItems 는 상품 관계는
241
+ * 거부하지만 **appStoreVersion 관계는 받는다** — 그래서 버전만 이쪽으로 옮기면 된다.
242
+ *
243
+ * 버전이 다른 묶음에 이미 물려 있으면 그 묶음을 먼저 정리해야 한다:
244
+ * - 미제출(READY_FOR_REVIEW) 묶음이면 appstore_remove_review_submission_item
245
+ * - 제출된(WAITING_FOR_REVIEW) 묶음이면 항목 제거가 막히므로 appstore_cancel_review 로 묶음째 취소
246
+ */
247
+ export declare function addVersionToReviewSubmission(args: {
248
+ submissionId: string;
249
+ versionId: string;
250
+ }): Promise<{
251
+ itemId?: string;
252
+ submissionId: string;
253
+ versionId: string;
254
+ itemCount: number;
255
+ }>;
215
256
  export declare function cancelVersionReview(versionId: string): Promise<{
216
257
  submissionId: string;
217
258
  previousState: string;
@@ -196,6 +196,33 @@ export async function createVersion(input) {
196
196
  createdDate: created?.data?.attributes?.createdDate,
197
197
  };
198
198
  }
199
+ /**
200
+ * 기존 버전 레코드의 versionString 을 바꾼다 (예: 2.0.5 → 2.0.6).
201
+ *
202
+ * 왜 필요한가: ASC 는 편집 가능한 버전이 이미 있으면 새 버전 생성을 거부한다
203
+ * 409 ENTITY_ERROR.RELATIONSHIP.INVALID "You cannot create a new version of the App in the current state"
204
+ * 그래서 거절/철회된 버전으로 다음 릴리스를 내보내려면 **같은 레코드의 이름을 올려야** 한다.
205
+ * 빌드는 CFBundleShortVersionString 이 일치하는 버전에만 붙으므로, 2.0.6 빌드를 올렸다면
206
+ * 버전 레코드도 2.0.6 이어야 attach 가 된다.
207
+ *
208
+ * 편집 가능한 상태(PREPARE_FOR_SUBMISSION / DEVELOPER_REJECTED / REJECTED 등)에서만 통한다.
209
+ */
210
+ export async function updateVersionString(versionId, versionString) {
211
+ const patched = await apiPatch(`/appStoreVersions/${versionId}`, {
212
+ data: {
213
+ type: 'appStoreVersions',
214
+ id: versionId,
215
+ attributes: { versionString },
216
+ },
217
+ });
218
+ const attrs = patched?.data?.attributes ?? {};
219
+ return {
220
+ versionId,
221
+ versionString: attrs.versionString ?? versionString,
222
+ state: attrs.appStoreState ?? attrs.appVersionState,
223
+ platform: attrs.platform,
224
+ };
225
+ }
199
226
  export async function attachBuildToVersion(versionId, buildId) {
200
227
  // /relationships/build 엔드포인트는 204 No Content 반환
201
228
  await apiPatch(`/appStoreVersions/${versionId}/relationships/build`, {
@@ -838,6 +865,41 @@ export async function removeReviewSubmissionItem(itemId) {
838
865
  removed: patched?.data?.attributes?.removed ?? true,
839
866
  };
840
867
  }
868
+ /**
869
+ * 이미 존재하는 묶음에 앱 버전을 항목으로 끼워 넣는다.
870
+ *
871
+ * 앱 첫 심사에서 반드시 필요하다. 웹에서 상품을 담으면 **상품만 든 묶음**이 새로 생기는데,
872
+ * 그대로 제출하면 Apple 이 막는다:
873
+ * 409 ENTITY_ERROR.RELATIONSHIP.REQUIRED
874
+ * "must have an approved appStoreVersions ... or an appStoreVersions must be included
875
+ * in this review submission"
876
+ * 즉 상품 묶음에는 앱 버전이 함께 있어야 한다. reviewSubmissionItems 는 상품 관계는
877
+ * 거부하지만 **appStoreVersion 관계는 받는다** — 그래서 버전만 이쪽으로 옮기면 된다.
878
+ *
879
+ * 버전이 다른 묶음에 이미 물려 있으면 그 묶음을 먼저 정리해야 한다:
880
+ * - 미제출(READY_FOR_REVIEW) 묶음이면 appstore_remove_review_submission_item
881
+ * - 제출된(WAITING_FOR_REVIEW) 묶음이면 항목 제거가 막히므로 appstore_cancel_review 로 묶음째 취소
882
+ */
883
+ export async function addVersionToReviewSubmission(args) {
884
+ const { submissionId, versionId } = args;
885
+ const created = await apiPost('/reviewSubmissionItems', {
886
+ data: {
887
+ type: 'reviewSubmissionItems',
888
+ relationships: {
889
+ reviewSubmission: { data: { type: 'reviewSubmissions', id: submissionId } },
890
+ appStoreVersion: { data: { type: 'appStoreVersions', id: versionId } },
891
+ },
892
+ },
893
+ });
894
+ // 제출 전에 몇 개가 들어있는지 보여준다 — 첫 심사에서 "상품이 빠졌는지"를 눈으로 확인해야 한다.
895
+ const items = await apiGet(`/reviewSubmissions/${submissionId}/items`, { limit: '50' }).catch(() => null);
896
+ return {
897
+ itemId: created?.data?.id,
898
+ submissionId,
899
+ versionId,
900
+ itemCount: (items?.data ?? []).length,
901
+ };
902
+ }
841
903
  /**
842
904
  * 버전을 물고 있는 낡은 묶음(UNRESOLVED_ISSUES)에서 해당 버전 항목을 removed=true 로
843
905
  * 풀어준다. 항목이 풀리면 Apple 이 옛 묶음을 COMPLETE 로 정리하고, 종종 새
@@ -554,12 +554,16 @@ export function registerAppstoreTools(server) {
554
554
  }],
555
555
  };
556
556
  });
557
- server.tool('appstore_upload_product_review_screenshot', '기존 App Store IAP/구독 상품의 심사용 스크린샷을 reserve → upload → commit. 상품당 1장, 절대 파일 경로 필요.', {
557
+ server.tool('appstore_upload_product_review_screenshot', [
558
+ '기존 App Store IAP/구독 상품의 심사용 스크린샷을 reserve → upload → commit. 상품당 1장, 절대 파일 경로 필요.',
559
+ '이미 있으면 409 "Screenshot already exists" — 갈아끼우려면 replace: true (기존 것을 지우고 올린다).',
560
+ ].join(' '), {
558
561
  appId: z.string().describe('App Store 앱 ID (숫자형, appstore_list_apps 결과)'),
559
562
  productId: z.string().describe('상품 ID (appstore_list_products 결과)'),
560
563
  productType: z.enum(['subscription', 'consumable', 'non_consumable']).describe('상품 유형'),
561
564
  filePath: z.string().describe('업로드할 PNG/JPG의 절대 파일 경로'),
562
- }, async ({ appId, productId, productType, filePath }) => {
565
+ replace: z.boolean().optional().describe('이미 스크린샷이 있으면 지우고 새로 올린다 (기본 false)'),
566
+ }, async ({ appId, productId, productType, filePath, replace }) => {
563
567
  const creds = requireAppStoreCreds();
564
568
  const products = await listAppleProducts({
565
569
  appId, keyId: creds.keyId, issuerId: creds.issuerId, privateKey: creds.privateKey,
@@ -572,6 +576,7 @@ export function registerAppstoreTools(server) {
572
576
  internalId: product.internalId,
573
577
  productType,
574
578
  filePath,
579
+ replace,
575
580
  });
576
581
  return {
577
582
  content: [{
@@ -581,6 +586,7 @@ export function registerAppstoreTools(server) {
581
586
  `productId: ${productId}`,
582
587
  `internalId: ${result.internalId}`,
583
588
  `screenshotId: ${result.id}`,
589
+ result.replacedId ? `교체됨 (이전 screenshotId: ${result.replacedId})` : '',
584
590
  `file: ${result.fileName} (${result.fileSize} bytes)`,
585
591
  result.state ? `state: ${result.state}` : '',
586
592
  result.verified ? '✓ commit 후 조회 확인' : '⚠ commit은 성공했지만 후속 조회는 확인하지 못함',
@@ -689,6 +695,53 @@ export function registerAppstoreTools(server) {
689
695
  }],
690
696
  };
691
697
  });
698
+ server.tool('appstore_add_version_to_review_submission', '이미 존재하는 심사 묶음에 앱 버전을 항목으로 추가한다 (POST /reviewSubmissionItems). ' +
699
+ '⚠️ 앱 첫 심사 필수 절차 — 웹에서 IAP/구독을 담으면 "상품만 든 묶음"이 새로 생기는데, ' +
700
+ '그대로 제출하면 409 "an appStoreVersions must be included in this review submission" 로 막힌다. ' +
701
+ 'reviewSubmissionItems 는 상품 관계는 거부하지만 appStoreVersion 관계는 받으므로, 버전을 이 묶음으로 옮기면 된다. ' +
702
+ '버전이 다른 묶음에 물려 있으면 먼저 풀 것: 미제출 묶음이면 appstore_remove_review_submission_item, ' +
703
+ '제출된 묶음이면 항목 제거가 막히므로 appstore_cancel_review 로 묶음째 취소. ' +
704
+ '추가 후 appstore_submit_for_review 로 제출하면 버전+상품이 한 묶음으로 나간다.', {
705
+ submissionId: z.string().describe('대상 reviewSubmission ID (appstore_list_review_submissions 결과)'),
706
+ versionId: z.string().describe('추가할 App Store 버전 ID (appstore_list_versions 결과)'),
707
+ }, async ({ submissionId, versionId }) => {
708
+ const result = await appstore.addVersionToReviewSubmission({ submissionId, versionId });
709
+ return {
710
+ content: [{
711
+ type: 'text',
712
+ text: [
713
+ '✓ 묶음에 앱 버전 추가됨',
714
+ `submissionId: ${result.submissionId}`,
715
+ `versionId: ${result.versionId}`,
716
+ result.itemId ? `itemId: ${result.itemId}` : '',
717
+ `현재 묶음 항목 수: ${result.itemCount}개`,
718
+ '제출 전 항목 수를 확인할 것 — 첫 심사라면 상품들이 함께 들어 있어야 한다.',
719
+ ].filter(Boolean).join('\n'),
720
+ }],
721
+ };
722
+ });
723
+ server.tool('appstore_update_version_string', '기존 App Store 버전 레코드의 versionString 을 변경 (예: 2.0.5 → 2.0.6). ' +
724
+ '⚠️ 편집 가능한 버전이 이미 있으면 appstore_create_version 이 409 "cannot create a new version in the current state" 로 막힌다 — ' +
725
+ '거절/철회된 버전으로 다음 릴리스를 내보내려면 새로 만들지 말고 이 도구로 **같은 레코드의 이름을 올린다**. ' +
726
+ '빌드는 CFBundleShortVersionString 이 같은 버전에만 붙으므로, 새 버전의 빌드를 attach 하려면 먼저 이걸 맞춰야 한다. ' +
727
+ 'PREPARE_FOR_SUBMISSION / DEVELOPER_REJECTED 등 편집 가능 상태에서만 통한다.', {
728
+ versionId: z.string().describe('App Store 버전 ID (appstore_list_versions 결과)'),
729
+ versionString: z.string().describe('새 버전 문자열 (예: "2.0.6")'),
730
+ }, async ({ versionId, versionString }) => {
731
+ const result = await appstore.updateVersionString(versionId, versionString);
732
+ return {
733
+ content: [{
734
+ type: 'text',
735
+ text: [
736
+ '✓ 버전 문자열 변경 완료',
737
+ `versionId: ${result.versionId}`,
738
+ `versionString: ${result.versionString}`,
739
+ result.state ? `state: ${result.state}` : '',
740
+ '이제 같은 버전의 빌드를 attach 할 수 있다 (appstore_attach_latest_build).',
741
+ ].filter(Boolean).join('\n'),
742
+ }],
743
+ };
744
+ });
692
745
  server.tool('appstore_update_product', 'App Store IAP 상품의 reference name 변경. productId / 유형은 변경 불가. ' +
693
746
  '스토어에 보이는 표시 이름·설명은 appstore_update_product_localization 을 쓴다.', {
694
747
  appId: z.string().optional().describe('App Store 앱 ID'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.13.13",
3
+ "version": "0.13.15",
4
4
  "description": "Mimi Seed MCP server — Firebase + AdMob + Google Play + App Store management for Claude Code / Codex / Cursor / any MCP client.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "등록된 MCP 도구 + 도메인 메타데이터(label·credential·summary)의 SSOT. src/__tests__/tool-manifest.test.ts 가 실제 서버 등록 목록과 diff 하고 메타데이터 정합성을 검사한다. 도구 추가/삭제/개명 시 이 파일을 함께 갱신할 것 — 산문 문서에는 정확한 개수를 쓰지 말고 이 파일을 가리킬 것. mimi-seed://tools/catalog 리소스가 이 파일을 그대로 서빙한다.",
3
- "total": 187,
3
+ "total": 189,
4
4
  "domains": {
5
5
  "admob": {
6
6
  "label": "AdMob",
@@ -78,6 +78,8 @@
78
78
  "appstore_submit_for_review",
79
79
  "appstore_list_review_submissions",
80
80
  "appstore_remove_review_submission_item",
81
+ "appstore_add_version_to_review_submission",
82
+ "appstore_update_version_string",
81
83
  "appstore_cancel_review"
82
84
  ]
83
85
  },