@yoonion/mimi-seed-mcp 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,13 +9,13 @@
9
9
  Claude Code:
10
10
 
11
11
  ```bash
12
- claude mcp add mimi-seed -- npx -y @yoonion/mimi-seed-mcp
12
+ claude mcp add mimi-seed-local -- npx -y @yoonion/mimi-seed-mcp
13
13
  ```
14
14
 
15
15
  Codex (`~/.codex/config.toml`):
16
16
 
17
17
  ```toml
18
- [mcp_servers.mimi-seed]
18
+ [mcp_servers.mimi-seed-local]
19
19
  command = "npx"
20
20
  args = ["-y", "@yoonion/mimi-seed-mcp"]
21
21
  enabled = true
@@ -26,7 +26,7 @@ Claude Desktop (`claude_desktop_config.json`):
26
26
  ```json
27
27
  {
28
28
  "mcpServers": {
29
- "mimi-seed": {
29
+ "mimi-seed-local": {
30
30
  "command": "npx",
31
31
  "args": ["-y", "@yoonion/mimi-seed-mcp"]
32
32
  }
@@ -62,16 +62,16 @@ export ANTHROPIC_API_KEY=sk-ant-...
62
62
 
63
63
  ---
64
64
 
65
- ## 제공 도구 (148개 · 17개 영역)
65
+ ## 제공 도구 (150+ 개 · 17개 영역)
66
66
 
67
67
  | 영역 | 도구 수 | 주요 도구 |
68
68
  |------|---------|-----------|
69
- | App Store Connect | 31 | `appstore_submit_for_review` / `appstore_upload_screenshot` / `appstore_update_whats_new` / `appstore_create_version` |
69
+ | App Store Connect | 33 | `appstore_submit_for_review` / `appstore_upload_screenshot` / `appstore_update_product_review_note` / `appstore_upload_product_review_screenshot` |
70
70
  | Google Play | 28 | `playstore_submit_release` / `playstore_promote_release` / `playstore_replace_images` / `playstore_reply_review` / `playstore_verify_service_account` |
71
71
  | Firebase | 20 | `firebase_create_project` / `firebase_create_android_app` / `firebase_get_android_config` / `firebase_create_ios_app` |
72
72
  | AdMob | 7 | `admob_list_apps` / `admob_create_ad_unit` / `admob_get_today_earnings` / `admob_get_report` |
73
73
  | CI/CD (GitHub Actions · GitLab) | 6 | `ci_trigger_build` / `ci_get_build_status` / `ci_list_workflows` / `ci_cancel_build` |
74
- | Jenkins (크리덴셜) | 6 | `jenkins_create_credential` / `jenkins_upload_keystore` / `jenkins_save_config` |
74
+ | Jenkins (크리덴셜 + 잡) | 10 | `jenkins_create_credential` / `jenkins_upload_keystore` / `jenkins_create_job` / `jenkins_update_job` |
75
75
  | GA4 | 6 | `ga4_create_property` / `ga4_create_data_stream` / `ga4_run_report` |
76
76
  | Search Console | 6 | `gsc_inspect_url` / `gsc_search_analytics` / `gsc_submit_sitemap` |
77
77
  | Google Ads | 6 | `googleads_list_campaigns` / `googleads_get_uac_report` / `googleads_get_campaign_report` |
@@ -84,7 +84,7 @@ export ANTHROPIC_API_KEY=sk-ant-...
84
84
  | 인증 | 3 | `mimi_seed_status` / `mimi_seed_auth_start` / `mimi_seed_auth_status` |
85
85
  | AI (Claude) | 2 | `generate_release_notes_from_commits` / `generate_review_reply` |
86
86
 
87
- > 인앱 결제(IAP·구독) 도구는 위 Play Store·App Store 카운트에 포함됩니다 — `playstore_create_subscription` · `appstore_create_inapp_purchase` (`@onesub/providers` 위임).
87
+ > 인앱 결제(IAP·구독) 도구는 위 Play Store·App Store 카운트에 포함됩니다 — `appstore_create_inapp_purchase` · `appstore_update_product_review_note` · `appstore_upload_product_review_screenshot` 등.
88
88
  > 전체 카탈로그(항상 최신): [`docs/domain/tool-catalog.md`](../../docs/domain/tool-catalog.md)
89
89
 
90
90
  ---
@@ -0,0 +1,28 @@
1
+ export type AppStoreProductType = 'subscription' | 'consumable' | 'non_consumable';
2
+ /** 기존 IAP/구독 상품의 App Review 노트를 수정한다. */
3
+ export declare function updateProductReviewNote(args: {
4
+ internalId: string;
5
+ productType: AppStoreProductType;
6
+ reviewNote: string;
7
+ }): Promise<{
8
+ internalId: string;
9
+ productType: AppStoreProductType;
10
+ state?: string;
11
+ }>;
12
+ /**
13
+ * 기존 IAP/구독 상품에 App Review 스크린샷을 reserve → upload → commit 한다.
14
+ * App Store Connect API는 상품당 심사용 스크린샷 하나를 허용한다.
15
+ */
16
+ export declare function uploadProductReviewScreenshot(args: {
17
+ internalId: string;
18
+ productType: AppStoreProductType;
19
+ filePath: string;
20
+ }): Promise<{
21
+ id: string;
22
+ internalId: string;
23
+ productType: AppStoreProductType;
24
+ fileName: string;
25
+ fileSize: number;
26
+ state?: string;
27
+ verified: boolean;
28
+ }>;
@@ -0,0 +1,179 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getAuthHeaders } from './auth.js';
5
+ import { friendlyAppStoreError } from './errors.js';
6
+ const V1_BASE = 'https://api.appstoreconnect.apple.com/v1';
7
+ const V2_BASE = 'https://api.appstoreconnect.apple.com/v2';
8
+ async function authHeadersOrThrow() {
9
+ const headers = await getAuthHeaders();
10
+ if (!headers) {
11
+ throw new Error([
12
+ '❌ App Store Connect 인증이 필요해.',
13
+ '',
14
+ '터미널에서 실행:',
15
+ ' npx -p @yoonion/mimi-seed-mcp mimi-seed-appstore-auth',
16
+ ].join('\n'));
17
+ }
18
+ return headers;
19
+ }
20
+ async function apiRequest(base, resourcePath, authHeaders, init) {
21
+ const response = await fetch(`${base}${resourcePath}`, {
22
+ ...init,
23
+ headers: { ...authHeaders, ...(init.headers ?? {}) },
24
+ });
25
+ if (!response.ok) {
26
+ const body = await response.text();
27
+ throw friendlyAppStoreError(response.status, body);
28
+ }
29
+ const text = await response.text();
30
+ return (text ? JSON.parse(text) : { ok: true });
31
+ }
32
+ function productResource(productType) {
33
+ if (productType === 'subscription') {
34
+ return { base: V1_BASE, type: 'subscriptions', path: '/subscriptions' };
35
+ }
36
+ return { base: V2_BASE, type: 'inAppPurchases', path: '/inAppPurchases' };
37
+ }
38
+ function reviewScreenshotResource(productType) {
39
+ if (productType === 'subscription') {
40
+ return {
41
+ type: 'subscriptionAppStoreReviewScreenshots',
42
+ path: '/subscriptionAppStoreReviewScreenshots',
43
+ relationship: 'subscription',
44
+ relatedType: 'subscriptions',
45
+ };
46
+ }
47
+ return {
48
+ type: 'inAppPurchaseAppStoreReviewScreenshots',
49
+ path: '/inAppPurchaseAppStoreReviewScreenshots',
50
+ relationship: 'inAppPurchaseV2',
51
+ relatedType: 'inAppPurchases',
52
+ };
53
+ }
54
+ /** 기존 IAP/구독 상품의 App Review 노트를 수정한다. */
55
+ export async function updateProductReviewNote(args) {
56
+ const { internalId, productType, reviewNote } = args;
57
+ const resource = productResource(productType);
58
+ const authHeaders = await authHeadersOrThrow();
59
+ const result = await apiRequest(resource.base, `${resource.path}/${internalId}`, authHeaders, {
60
+ method: 'PATCH',
61
+ headers: { 'Content-Type': 'application/json' },
62
+ body: JSON.stringify({
63
+ data: {
64
+ type: resource.type,
65
+ id: internalId,
66
+ attributes: { reviewNote },
67
+ },
68
+ }),
69
+ });
70
+ return { internalId, productType, state: result.data?.attributes?.state };
71
+ }
72
+ async function uploadChunks(buffer, operations) {
73
+ for (const operation of operations) {
74
+ const uploadUrl = new URL(operation.url);
75
+ if (uploadUrl.protocol !== 'https:') {
76
+ throw new Error(`HTTPS가 아닌 업로드 URL은 거부해: ${uploadUrl.protocol}`);
77
+ }
78
+ if (operation.offset < 0 ||
79
+ operation.length <= 0 ||
80
+ operation.offset + operation.length > buffer.length) {
81
+ throw new Error(`잘못된 업로드 청크 범위: offset=${operation.offset}, ` +
82
+ `length=${operation.length}, fileSize=${buffer.length}`);
83
+ }
84
+ const chunk = buffer.subarray(operation.offset, operation.offset + operation.length);
85
+ const headers = {};
86
+ for (const header of operation.requestHeaders)
87
+ headers[header.name] = header.value;
88
+ const response = await fetch(operation.url, {
89
+ method: operation.method,
90
+ headers,
91
+ body: new Uint8Array(chunk),
92
+ });
93
+ if (!response.ok) {
94
+ const body = await response.text();
95
+ throw new Error(`청크 업로드 실패 (offset=${operation.offset}, length=${operation.length}): ` +
96
+ `${response.status} ${body}`);
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * 기존 IAP/구독 상품에 App Review 스크린샷을 reserve → upload → commit 한다.
102
+ * App Store Connect API는 상품당 심사용 스크린샷 하나를 허용한다.
103
+ */
104
+ export async function uploadProductReviewScreenshot(args) {
105
+ const { internalId, productType, filePath } = args;
106
+ if (!path.isAbsolute(filePath)) {
107
+ throw new Error(`절대 경로가 필요해: ${filePath}`);
108
+ }
109
+ if (!fs.existsSync(filePath)) {
110
+ throw new Error(`파일이 존재하지 않아: ${filePath}`);
111
+ }
112
+ const buffer = fs.readFileSync(filePath);
113
+ const fileName = path.basename(filePath);
114
+ const fileSize = buffer.length;
115
+ if (!/\.(png|jpe?g)$/i.test(fileName)) {
116
+ throw new Error(`PNG/JPG 파일만 업로드할 수 있어: ${fileName}`);
117
+ }
118
+ if (fileSize === 0) {
119
+ throw new Error(`빈 파일은 업로드할 수 없어: ${fileName}`);
120
+ }
121
+ const checksum = crypto.createHash('md5').update(buffer).digest('hex');
122
+ const resource = reviewScreenshotResource(productType);
123
+ const authHeaders = await authHeadersOrThrow();
124
+ const reserved = await apiRequest(V1_BASE, resource.path, authHeaders, {
125
+ method: 'POST',
126
+ headers: { 'Content-Type': 'application/json' },
127
+ body: JSON.stringify({
128
+ data: {
129
+ type: resource.type,
130
+ attributes: { fileName, fileSize },
131
+ relationships: {
132
+ [resource.relationship]: {
133
+ data: { type: resource.relatedType, id: internalId },
134
+ },
135
+ },
136
+ },
137
+ }),
138
+ });
139
+ const screenshotId = reserved.data.id;
140
+ const operations = reserved.data.attributes?.uploadOperations ?? [];
141
+ if (operations.length === 0) {
142
+ await apiRequest(V1_BASE, `${resource.path}/${screenshotId}`, authHeaders, {
143
+ method: 'DELETE',
144
+ }).catch(() => undefined);
145
+ throw new Error('uploadOperations가 비어있음 — Apple API 응답 형식 확인 필요.');
146
+ }
147
+ try {
148
+ await uploadChunks(buffer, operations);
149
+ await apiRequest(V1_BASE, `${resource.path}/${screenshotId}`, authHeaders, {
150
+ method: 'PATCH',
151
+ headers: { 'Content-Type': 'application/json' },
152
+ body: JSON.stringify({
153
+ data: {
154
+ type: resource.type,
155
+ id: screenshotId,
156
+ attributes: { uploaded: true, sourceFileChecksum: checksum },
157
+ },
158
+ }),
159
+ });
160
+ }
161
+ catch (error) {
162
+ // 완료되지 않은 reservation이 남지 않도록 best-effort 정리.
163
+ await apiRequest(V1_BASE, `${resource.path}/${screenshotId}`, authHeaders, {
164
+ method: 'DELETE',
165
+ }).catch(() => undefined);
166
+ throw error;
167
+ }
168
+ let state;
169
+ let verified = false;
170
+ try {
171
+ const confirmed = await apiRequest(V1_BASE, `${resource.path}/${screenshotId}`, authHeaders, { method: 'GET' });
172
+ state = confirmed.data.attributes?.assetDeliveryState?.state;
173
+ verified = true;
174
+ }
175
+ catch {
176
+ // commit 성공 후 확인 GET만 실패하면 재업로드를 유도하지 않는다.
177
+ }
178
+ return { id: screenshotId, internalId, productType, fileName, fileSize, state, verified };
179
+ }
package/dist/helpers.js CHANGED
@@ -4,12 +4,12 @@ import { getAppStoreCredentials } from './appstore/auth.js';
4
4
  export { ensureFreshAccessToken };
5
5
  const REAUTH_CMD = ' npx -y @yoonion/mimi-seed-mcp mimi-seed-auth';
6
6
  function formatAuthError(p) {
7
+ // 재로그인 안내는 needsReauth 인 경우에만 — CONFIG_FETCH_FAILED 처럼 재로그인이
8
+ // 해법이 아닌 에러에 무조건 붙이면 같은 실패를 반복하게 만든다.
7
9
  return [
8
10
  `❌ [${p.code}] ${p.message}`,
9
11
  p.hint ? `→ ${p.hint}` : '',
10
- '',
11
- '터미널에서 재로그인:',
12
- REAUTH_CMD,
12
+ ...(p.needsReauth ? ['', '터미널에서 재로그인:', REAUTH_CMD] : []),
13
13
  ]
14
14
  .filter((l) => l !== '')
15
15
  .join('\n');
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import * as appstore from '../appstore/tools.js';
3
3
  import * as appstoreScreenshots from '../appstore/screenshots.js';
4
+ import * as appstoreProductReview from '../appstore/product-review.js';
4
5
  import { createAppleOneTimePurchase, createAppleSubscription, updateAppleProduct, deleteAppleProduct, listAppleProducts, } from '@onesub/providers';
5
6
  import { requireAppStoreCreds } from '../helpers.js';
6
7
  import { buildAppStoreReleasePlan } from '../checks/plan.js';
@@ -440,6 +441,71 @@ export function registerAppstoreTools(server) {
440
441
  });
441
442
  return { content: [{ type: 'text', text: JSON.stringify(products, null, 2) }] };
442
443
  });
444
+ server.tool('appstore_update_product_review_note', '기존 App Store IAP/구독 상품의 App Review 노트를 수정. appstore_list_products의 productId/type을 사용.', {
445
+ appId: z.string().describe('App Store 앱 ID (숫자형, appstore_list_apps 결과)'),
446
+ productId: z.string().describe('상품 ID (appstore_list_products 결과)'),
447
+ productType: z.enum(['subscription', 'consumable', 'non_consumable']).describe('상품 유형'),
448
+ reviewNote: z.string().max(4000).describe('Apple 심사용 노트 (4000자 이하, 빈 문자열은 초기화)'),
449
+ }, async ({ appId, productId, productType, reviewNote }) => {
450
+ const creds = requireAppStoreCreds();
451
+ const products = await listAppleProducts({
452
+ appId, keyId: creds.keyId, issuerId: creds.issuerId, privateKey: creds.privateKey,
453
+ });
454
+ const product = products.find((item) => item.productId === productId && item.type === productType);
455
+ if (!product) {
456
+ return { content: [{ type: 'text', text: `상품을 찾을 수 없음: ${productId} (${productType})` }] };
457
+ }
458
+ const result = await appstoreProductReview.updateProductReviewNote({
459
+ internalId: product.internalId,
460
+ productType,
461
+ reviewNote,
462
+ });
463
+ return {
464
+ content: [{
465
+ type: 'text',
466
+ text: [
467
+ '✓ App Review 노트 수정 완료',
468
+ `productId: ${productId}`,
469
+ `internalId: ${result.internalId}`,
470
+ result.state ? `state: ${result.state}` : '',
471
+ ].filter(Boolean).join('\n'),
472
+ }],
473
+ };
474
+ });
475
+ server.tool('appstore_upload_product_review_screenshot', '기존 App Store IAP/구독 상품의 심사용 스크린샷을 reserve → upload → commit. 상품당 1장, 절대 파일 경로 필요.', {
476
+ appId: z.string().describe('App Store 앱 ID (숫자형, appstore_list_apps 결과)'),
477
+ productId: z.string().describe('상품 ID (appstore_list_products 결과)'),
478
+ productType: z.enum(['subscription', 'consumable', 'non_consumable']).describe('상품 유형'),
479
+ filePath: z.string().describe('업로드할 PNG/JPG의 절대 파일 경로'),
480
+ }, async ({ appId, productId, productType, filePath }) => {
481
+ const creds = requireAppStoreCreds();
482
+ const products = await listAppleProducts({
483
+ appId, keyId: creds.keyId, issuerId: creds.issuerId, privateKey: creds.privateKey,
484
+ });
485
+ const product = products.find((item) => item.productId === productId && item.type === productType);
486
+ if (!product) {
487
+ return { content: [{ type: 'text', text: `상품을 찾을 수 없음: ${productId} (${productType})` }] };
488
+ }
489
+ const result = await appstoreProductReview.uploadProductReviewScreenshot({
490
+ internalId: product.internalId,
491
+ productType,
492
+ filePath,
493
+ });
494
+ return {
495
+ content: [{
496
+ type: 'text',
497
+ text: [
498
+ '✓ App Review 스크린샷 업로드 완료',
499
+ `productId: ${productId}`,
500
+ `internalId: ${result.internalId}`,
501
+ `screenshotId: ${result.id}`,
502
+ `file: ${result.fileName} (${result.fileSize} bytes)`,
503
+ result.state ? `state: ${result.state}` : '',
504
+ result.verified ? '✓ commit 후 조회 확인' : '⚠ commit은 성공했지만 후속 조회는 확인하지 못함',
505
+ ].filter(Boolean).join('\n'),
506
+ }],
507
+ };
508
+ });
443
509
  server.tool('appstore_update_product', 'App Store IAP 상품의 reference name 변경. productId / 유형은 변경 불가.', {
444
510
  appId: z.string().optional().describe('App Store 앱 ID'),
445
511
  bundleId: z.string().optional().describe('번들 ID (appId 대신 사용 가능)'),
@@ -1,4 +1,6 @@
1
+ import { readFileSync } from 'node:fs';
1
2
  import { getMcpOAuthClient } from '../auth/constants.js';
3
+ import { classifyError } from '../auth/errors.js';
2
4
  import { startAuth, ensureFreshAccessToken, getTokensLastRefreshMs } from '../auth/google-auth.js';
3
5
  import { listRegisteredServiceAccounts } from '../auth/playstore-auth.js';
4
6
  import { getAppStoreCredentials } from '../appstore/auth.js';
@@ -43,8 +45,11 @@ const MANIFEST_FIX = {
43
45
  ? `setup_playstore_connection(packageName="${svc.packageName}")`
44
46
  : 'setup_playstore_connection(packageName=...)',
45
47
  appstore: () => 'npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth',
46
- jenkins: () => 'claude mcp add virgm-jenkins -s user (개인 자격증명 — .mcp.json 금지)',
48
+ jenkins: () => 'claude mcp add <your-jenkins-mcp> -s user (개인 자격증명 — .mcp.json 금지)',
47
49
  };
50
+ // 두 MCP 가 모두 'mimi-seed' 로 등록되는 환경에서 에이전트가 프로그램적으로
51
+ // 어느 서버인지 판별할 수 있도록 status 첫 줄에 자기소개를 넣는다.
52
+ const { version: PKG_VERSION } = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
48
53
  /** 서비스별 식별자를 한 줄 detail 로 (예: "ads-coffee / analytics_530080532"). */
49
54
  function manifestServiceDetail(id, svc) {
50
55
  const parts = [];
@@ -91,7 +96,10 @@ export function registerAuthTools(server) {
91
96
  '설정 상태를 한 번에 스캔해 ✅ / ❌ 트래픽 라이트 리포트와 번호 매긴 다음 단계를 반환합니다.',
92
97
  '미설정 서비스마다 어떤 도구를 호출하면 되는지 구체적으로 알려줍니다.',
93
98
  ].join(' '), {}, async () => {
94
- const lines = ['🌱 Mimi Seed 연결 상태', ''];
99
+ const lines = [
100
+ `🌱 Mimi Seed 연결 상태 — local-stdio (@yoonion/mimi-seed-mcp v${PKG_VERSION})`,
101
+ '',
102
+ ];
95
103
  // 1. Google OAuth
96
104
  const oauthResult = await ensureFreshAccessToken();
97
105
  if (oauthResult.status === 'fresh' || oauthResult.status === 'refreshed') {
@@ -121,7 +129,7 @@ export function registerAuthTools(server) {
121
129
  lines.push(`✅ App Store Connect — 연결됨 (keyId: ${asc.keyId})`);
122
130
  }
123
131
  else {
124
- lines.push('❌ App Store Connect — 미설정 → npx @yoonion/mimi-seed-mcp mimi-seed-appstore-auth');
132
+ lines.push('❌ App Store Connect — 미설정 → npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth');
125
133
  }
126
134
  // 4. Jenkins
127
135
  const jenkins = loadJenkinsConfig();
@@ -173,7 +181,7 @@ export function registerAuthTools(server) {
173
181
  lines.push('⚠️ BigQuery — OAuth fallback (Workspace 재인증 정책 시 끊길 수 있음 → 서비스 계정 권장: mimi-seed-bigquery-auth)');
174
182
  }
175
183
  else {
176
- lines.push('❌ BigQuery — 미설정 → npx @yoonion/mimi-seed-mcp mimi-seed-bigquery-auth (선택)');
184
+ lines.push('❌ BigQuery — 미설정 → npx -y @yoonion/mimi-seed-mcp mimi-seed-bigquery-auth (선택)');
177
185
  }
178
186
  // 다음 단계 안내
179
187
  const missing = [];
@@ -184,7 +192,7 @@ export function registerAuthTools(server) {
184
192
  missing.push(' 2. setup_playstore_connection(packageName=..., projectId=...) (Play Store 배포 필수)');
185
193
  }
186
194
  if (!asc) {
187
- missing.push(' 3. npx @yoonion/mimi-seed-mcp mimi-seed-appstore-auth (App Store 배포 필수)');
195
+ missing.push(' 3. npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth (App Store 배포 필수)');
188
196
  }
189
197
  if (missing.length > 0) {
190
198
  lines.push('', '── 다음 단계 (필수) ─────────────────────────────', ...missing);
@@ -230,7 +238,21 @@ export function registerAuthTools(server) {
230
238
  '이후 playstore_*, firebase_*, admob_* 등 다른 MCP 도구 바로 호출 가능.',
231
239
  '토큰 만료(invalid_rapt) / 재인증 필요 시 사용. 10분 내 완료해야 함.',
232
240
  ].join(' '), {}, async () => {
233
- const { clientId, clientSecret } = await getMcpOAuthClient();
241
+ // 설정 조회 실패를 분류된 안내로 raw throw 는 MCP 클라이언트에 마커 문자열만 노출된다.
242
+ let clientId;
243
+ let clientSecret;
244
+ try {
245
+ ({ clientId, clientSecret } = await getMcpOAuthClient());
246
+ }
247
+ catch (e) {
248
+ const p = classifyError(e, { phase: 'login' });
249
+ return {
250
+ content: [{
251
+ type: 'text',
252
+ text: `❌ [${p.code}] ${p.message}${p.hint ? `\n→ ${p.hint}` : ''}`,
253
+ }],
254
+ };
255
+ }
234
256
  const { url, wait } = startAuth(clientId, clientSecret);
235
257
  // fire-and-forget — 토큰은 콜백 서버가 자동 저장
236
258
  wait.then(() => { }, (err) => { console.error('[mimi-seed auth]', err.message); });
@@ -290,8 +312,11 @@ export function registerAuthTools(server) {
290
312
  ` 코드: ${r.error.code}\n` +
291
313
  ` ${r.error.message}\n` +
292
314
  (r.error.hint ? ` → ${r.error.hint}\n` : '') +
293
- `${refreshLine}\n` +
294
- '\n터미널에서 재로그인:\n npx -y @yoonion/mimi-seed-mcp mimi-seed-auth',
315
+ `${refreshLine}` +
316
+ // 재로그인 안내는 그것이 실제 해법일 때만 (네트워크/설정 조회 실패엔 무의미)
317
+ (r.error.needsReauth
318
+ ? '\n\n터미널에서 재로그인:\n npx -y @yoonion/mimi-seed-mcp mimi-seed-auth'
319
+ : ''),
295
320
  }],
296
321
  };
297
322
  }
package/dist/resources.js CHANGED
@@ -25,7 +25,7 @@ export function registerResources(server) {
25
25
  '# Mimi Seed — 앱 출시·운영 Agent',
26
26
  '',
27
27
  '당신은 Mimi Seed MCP를 통해 인디 개발자의 앱 출시와 운영을 돕는 에이전트입니다.',
28
- 'Google Play · App Store · Firebase · AdMob · CI/CD · BigQuery를 직접 제어하는 110+ 도구를 사용할 수 있습니다.',
28
+ 'Google Play · App Store · Firebase · AdMob · CI/CD · BigQuery를 직접 제어하는 150+ 도구를 사용할 수 있습니다.',
29
29
  '',
30
30
  '## 출시 요청 처리 순서',
31
31
  '',
package/dist/server.js CHANGED
@@ -24,8 +24,11 @@ import { registerResources } from './resources.js';
24
24
  * 여기(index.ts 가 아니라)에 추가해야 테스트가 tool-manifest.json 과의 드리프트를 잡는다.
25
25
  */
26
26
  export function buildServer(version) {
27
+ // serverInfo.name 은 'mimi-seed-local' — 웹 콘솔의 Remote HTTP MCP(mimi-seed-web)와
28
+ // 핸드셰이크 수준에서 구분한다. 클라이언트 표시명/도구 네임스페이스(mcp__mimi-seed__*)는
29
+ // 등록 키(.mcp.json / claude mcp add 의 이름)에서 오므로 이 값 변경은 호환성에 영향 없음.
27
30
  const server = new McpServer({
28
- name: 'mimi-seed',
31
+ name: 'mimi-seed-local',
29
32
  version,
30
33
  });
31
34
  registerFirebaseTools(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
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": {