@yoonion/mimi-seed-mcp 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/appstore/auth.d.ts +31 -0
- package/dist/appstore/auth.js +21 -0
- package/dist/appstore/sales.d.ts +13 -0
- package/dist/appstore/sales.js +89 -4
- package/dist/registers/appstore.js +17 -1
- package/package.json +1 -1
package/dist/appstore/auth.d.ts
CHANGED
|
@@ -9,8 +9,39 @@ export interface AppStoreCredentials {
|
|
|
9
9
|
* 여기 적어두는 수밖에 없다. 리포트 도구에만 쓰이므로 없어도 나머지는 다 동작한다.
|
|
10
10
|
*/
|
|
11
11
|
vendorNumber?: string;
|
|
12
|
+
/**
|
|
13
|
+
* 매출 리포트 전용 별도 키 (선택).
|
|
14
|
+
*
|
|
15
|
+
* 리포트 엔드포인트는 다른 ASC API 와 요구 롤이 다르다 —
|
|
16
|
+
* **Admin / Finance / Sales and Reports** 중 하나여야 한다. 배포에 쓰는 키는 보통
|
|
17
|
+
* App Manager 라 여기서만 403 이 나는데, **Apple 은 발급된 키의 롤을 수정할 수 없게**
|
|
18
|
+
* 해놨다(폐기 후 재발급만 가능).
|
|
19
|
+
*
|
|
20
|
+
* 그렇다고 배포 키를 Admin 으로 갈아끼우면 잘 돌던 릴리스 파이프라인의 자격증명을
|
|
21
|
+
* 전부 교체해야 하고, 권한도 사용자·재무까지 넓어진다. 그래서 **읽기 전용 Finance 키를
|
|
22
|
+
* 따로 두고 리포트 도구만 이걸 쓰게** 한다. 배포 키는 손대지 않는다.
|
|
23
|
+
*
|
|
24
|
+
* 없으면 최상위 키로 폴백하므로, 배포 키가 이미 Admin 이면 설정할 필요가 없다.
|
|
25
|
+
*/
|
|
26
|
+
reportsKey?: AppStoreKey;
|
|
27
|
+
}
|
|
28
|
+
/** ASC API 키 한 벌. 최상위 자격증명과 reportsKey 가 같은 모양을 공유한다. */
|
|
29
|
+
export interface AppStoreKey {
|
|
30
|
+
issuerId: string;
|
|
31
|
+
keyId: string;
|
|
32
|
+
privateKey: string;
|
|
12
33
|
}
|
|
13
34
|
export declare function getAppStoreCredentials(): AppStoreCredentials | null;
|
|
14
35
|
export declare function saveAppStoreCredentials(creds: AppStoreCredentials): void;
|
|
15
36
|
export declare function generateToken(creds: AppStoreCredentials): Promise<string>;
|
|
16
37
|
export declare function getAuthHeaders(): Promise<Record<string, string> | null>;
|
|
38
|
+
/**
|
|
39
|
+
* 매출 리포트용 헤더 — reportsKey 가 있으면 그걸로, 없으면 최상위 키로 폴백.
|
|
40
|
+
*
|
|
41
|
+
* 폴백이 조용하면 안 된다: 403 이 났을 때 "어느 키가 거부당했는지" 를 모르면 사용자가
|
|
42
|
+
* 엉뚱한 키의 롤을 들여다보게 된다. 그래서 어느 키를 썼는지 함께 돌려준다.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getReportsAuthHeaders(): Promise<{
|
|
45
|
+
headers: Record<string, string>;
|
|
46
|
+
source: 'reportsKey' | 'default';
|
|
47
|
+
} | null>;
|
package/dist/appstore/auth.js
CHANGED
|
@@ -66,3 +66,24 @@ export async function getAuthHeaders() {
|
|
|
66
66
|
const token = await generateToken(creds);
|
|
67
67
|
return { Authorization: `Bearer ${token}` };
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* 매출 리포트용 헤더 — reportsKey 가 있으면 그걸로, 없으면 최상위 키로 폴백.
|
|
71
|
+
*
|
|
72
|
+
* 폴백이 조용하면 안 된다: 403 이 났을 때 "어느 키가 거부당했는지" 를 모르면 사용자가
|
|
73
|
+
* 엉뚱한 키의 롤을 들여다보게 된다. 그래서 어느 키를 썼는지 함께 돌려준다.
|
|
74
|
+
*/
|
|
75
|
+
export async function getReportsAuthHeaders() {
|
|
76
|
+
const creds = getAppStoreCredentials();
|
|
77
|
+
if (!creds)
|
|
78
|
+
return null;
|
|
79
|
+
if (creds.reportsKey) {
|
|
80
|
+
return {
|
|
81
|
+
headers: { Authorization: `Bearer ${await generateToken(creds.reportsKey)}` },
|
|
82
|
+
source: 'reportsKey',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
headers: { Authorization: `Bearer ${await generateToken(creds)}` },
|
|
87
|
+
source: 'default',
|
|
88
|
+
};
|
|
89
|
+
}
|
package/dist/appstore/sales.d.ts
CHANGED
|
@@ -40,6 +40,19 @@ export declare function getSalesReport(options: {
|
|
|
40
40
|
reportSubType?: string;
|
|
41
41
|
version?: string;
|
|
42
42
|
}): Promise<SalesSummary>;
|
|
43
|
+
/**
|
|
44
|
+
* 리포트 접근 가능 여부만 싸게 확인한다 (appstore_verify_credentials 용).
|
|
45
|
+
*
|
|
46
|
+
* 왜 별도 프로브가 필요한가: 리포트 엔드포인트는 **다른 ASC API 와 요구 롤이 다르다.**
|
|
47
|
+
* App Manager 키는 GET /apps 가 멀쩡히 되므로 기존 검증은 전부 통과하는데, 매출 도구만
|
|
48
|
+
* 403 으로 죽는다. 그 사실이 첫 매출 조회 때까지 드러나지 않으면 "도구가 고장났다" 로
|
|
49
|
+
* 오진하게 된다 — Play 쪽 playstore_verify_service_account 가 'View financial data' 를
|
|
50
|
+
* 함께 확인하는 것과 같은 이유다.
|
|
51
|
+
*/
|
|
52
|
+
export declare function probeReportsAccess(): Promise<{
|
|
53
|
+
status: 'ok' | 'no_vendor_number' | 'forbidden' | 'error';
|
|
54
|
+
detail: string;
|
|
55
|
+
}>;
|
|
43
56
|
export declare function getFinanceReport(options: {
|
|
44
57
|
vendorNumber?: string;
|
|
45
58
|
/** YYYY-MM. **Apple 회계월**이라 달력월과 어긋날 수 있다 — 도구 설명 참고. */
|
package/dist/appstore/sales.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// sandbox 가 들어오지 않는 창구를 봐야 하고, 그게 이 리포트다.
|
|
10
10
|
import zlib from 'node:zlib';
|
|
11
11
|
import { fetchWithTimeout } from '../lib/http.js';
|
|
12
|
-
import { getAppStoreCredentials } from './auth.js';
|
|
12
|
+
import { getAppStoreCredentials, getReportsAuthHeaders } from './auth.js';
|
|
13
13
|
import { friendlyAppStoreError } from './errors.js';
|
|
14
|
-
import {
|
|
14
|
+
import { V1_BASE } from './http.js';
|
|
15
15
|
/**
|
|
16
16
|
* vendorNumber 해석: 명시 인자 > ~/.mimi-seed/appstore.json 의 vendorNumber.
|
|
17
17
|
*
|
|
@@ -41,13 +41,44 @@ function resolveVendorNumber(explicit) {
|
|
|
41
41
|
* 여기서는 빈 배열로 뭉개지 않고 notFound 플래그를 그대로 올려보낸다.
|
|
42
42
|
*/
|
|
43
43
|
async function fetchReport(resourcePath, params) {
|
|
44
|
-
const
|
|
44
|
+
const auth = await getReportsAuthHeaders();
|
|
45
|
+
if (!auth) {
|
|
46
|
+
throw new Error([
|
|
47
|
+
'❌ App Store Connect 인증이 필요해.',
|
|
48
|
+
'',
|
|
49
|
+
'터미널에서 실행:',
|
|
50
|
+
' npx -p @yoonion/mimi-seed-mcp mimi-seed-appstore-auth',
|
|
51
|
+
].join('\n'));
|
|
52
|
+
}
|
|
45
53
|
const query = new URLSearchParams(params).toString();
|
|
46
54
|
const response = await fetchWithTimeout(`${V1_BASE}${resourcePath}?${query}`, {
|
|
47
|
-
headers: { ...
|
|
55
|
+
headers: { ...auth.headers, Accept: 'application/a-gzip' },
|
|
48
56
|
});
|
|
49
57
|
if (response.status === 404)
|
|
50
58
|
return { notFound: true, rows: [], raw: '' };
|
|
59
|
+
if (response.status === 403) {
|
|
60
|
+
// 일반 403 안내("키 role 확인")로는 못 고친다 — 리포트 엔드포인트만 요구 롤이 다르다.
|
|
61
|
+
// 앱 메타데이터가 멀쩡히 조회되는 키에서도 여기서만 막히는 게 정상 동작이라,
|
|
62
|
+
// "키가 깨졌나" 로 헤매기 쉽다.
|
|
63
|
+
throw new Error([
|
|
64
|
+
'❌ 매출 리포트 접근 거부 (403) — 키는 정상인데 **롤이 부족**하다.',
|
|
65
|
+
` 거부당한 키: ${auth.source === 'reportsKey' ? 'reportsKey (리포트 전용)' : '최상위 키 (배포용과 동일)'}`,
|
|
66
|
+
'',
|
|
67
|
+
'리포트 엔드포인트는 다른 App Store Connect API 와 요구 롤이 다르다:',
|
|
68
|
+
' 필요: **Admin / Finance / Sales and Reports(ACCESS_TO_REPORTS)** 중 하나',
|
|
69
|
+
'App Manager·Developer 키는 앱 메타데이터는 다 되는데 여기서만 막힌다 —',
|
|
70
|
+
'다른 도구가 잘 도는 것은 이 403 과 아무 관계가 없다.',
|
|
71
|
+
'',
|
|
72
|
+
'⚠️ **발급된 키의 롤은 수정할 수 없다** (Apple 정책 — 폐기 후 재발급만 가능).',
|
|
73
|
+
'그렇다고 배포 키를 갈아끼우면 릴리스 파이프라인 자격증명을 전부 교체해야 한다.',
|
|
74
|
+
'',
|
|
75
|
+
'권장: **읽기 전용 Finance 키를 따로 발급**해 리포트 도구만 쓰게 한다.',
|
|
76
|
+
' 1) ASC > 사용자 및 액세스 > 통합 > 팀 키 > 키 생성, 액세스 = Finance',
|
|
77
|
+
' 2) 받은 .p8 내용을 ~/.mimi-seed/appstore.json 의 reportsKey 에 넣는다:',
|
|
78
|
+
' "reportsKey": { "issuerId": "...", "keyId": "...", "privateKey": "-----BEGIN..." }',
|
|
79
|
+
'배포 키는 그대로 두면 된다 — 리포트 도구만 reportsKey 를 쓴다.',
|
|
80
|
+
].join('\n'));
|
|
81
|
+
}
|
|
51
82
|
if (!response.ok) {
|
|
52
83
|
throw friendlyAppStoreError(response.status, await response.text());
|
|
53
84
|
}
|
|
@@ -165,6 +196,60 @@ export async function getSalesReport(options) {
|
|
|
165
196
|
}
|
|
166
197
|
return { datesWithData, datesWithoutData, lines, proceedsByCurrency, paidUnits, freeUnits };
|
|
167
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* 리포트 접근 가능 여부만 싸게 확인한다 (appstore_verify_credentials 용).
|
|
201
|
+
*
|
|
202
|
+
* 왜 별도 프로브가 필요한가: 리포트 엔드포인트는 **다른 ASC API 와 요구 롤이 다르다.**
|
|
203
|
+
* App Manager 키는 GET /apps 가 멀쩡히 되므로 기존 검증은 전부 통과하는데, 매출 도구만
|
|
204
|
+
* 403 으로 죽는다. 그 사실이 첫 매출 조회 때까지 드러나지 않으면 "도구가 고장났다" 로
|
|
205
|
+
* 오진하게 된다 — Play 쪽 playstore_verify_service_account 가 'View financial data' 를
|
|
206
|
+
* 함께 확인하는 것과 같은 이유다.
|
|
207
|
+
*/
|
|
208
|
+
export async function probeReportsAccess() {
|
|
209
|
+
let vendorNumber;
|
|
210
|
+
try {
|
|
211
|
+
vendorNumber = resolveVendorNumber();
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return {
|
|
215
|
+
status: 'no_vendor_number',
|
|
216
|
+
detail: 'vendorNumber 미설정 — ~/.mimi-seed/appstore.json 에 추가하면 매출 리포트를 쓸 수 있다.',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// 어제 날짜 하루치. 데이터가 없어 404 여도 **권한 확인 목적은 달성**된다 —
|
|
220
|
+
// 403 이 아니라는 것 자체가 롤이 충분하다는 뜻이다.
|
|
221
|
+
const probeDate = new Date(Date.now() - 86_400_000).toISOString().slice(0, 10);
|
|
222
|
+
try {
|
|
223
|
+
await fetchReport('/salesReports', {
|
|
224
|
+
'filter[frequency]': 'DAILY',
|
|
225
|
+
'filter[reportType]': 'SALES',
|
|
226
|
+
'filter[reportSubType]': 'SUMMARY',
|
|
227
|
+
'filter[vendorNumber]': vendorNumber,
|
|
228
|
+
'filter[reportDate]': probeDate,
|
|
229
|
+
'filter[version]': '1_0',
|
|
230
|
+
});
|
|
231
|
+
const usingSeparateKey = getAppStoreCredentials()?.reportsKey != null;
|
|
232
|
+
return {
|
|
233
|
+
status: 'ok',
|
|
234
|
+
detail: `매출 리포트 접근 가능 (vendorNumber ${vendorNumber}, ` +
|
|
235
|
+
`${usingSeparateKey ? 'reportsKey 사용' : '최상위 키 사용'})`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
const message = err?.message ?? '';
|
|
240
|
+
if (message.includes('403')) {
|
|
241
|
+
const usingSeparateKey = getAppStoreCredentials()?.reportsKey != null;
|
|
242
|
+
return {
|
|
243
|
+
status: 'forbidden',
|
|
244
|
+
detail: '매출 리포트 403 — 키 롤 부족 (Admin / Finance / Sales and Reports 필요). ' +
|
|
245
|
+
(usingSeparateKey
|
|
246
|
+
? 'reportsKey 가 거부당했다.'
|
|
247
|
+
: 'reportsKey 를 따로 두면 배포 키를 건드리지 않아도 된다.'),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return { status: 'error', detail: message.split('\n')[0] };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
168
253
|
export async function getFinanceReport(options) {
|
|
169
254
|
return fetchReport('/financeReports', {
|
|
170
255
|
'filter[vendorNumber]': resolveVendorNumber(options.vendorNumber),
|
|
@@ -24,9 +24,18 @@ export function registerAppstoreTools(server) {
|
|
|
24
24
|
const apps = await appstore.listApps();
|
|
25
25
|
return jsonResult(apps);
|
|
26
26
|
});
|
|
27
|
-
server.tool('appstore_verify_credentials',
|
|
27
|
+
server.tool('appstore_verify_credentials', [
|
|
28
|
+
'App Store Connect API 키(appstore.json) 유효성 검증 — JWT 서명 + GET /apps 호출로',
|
|
29
|
+
'creds/sign/auth/api 단계별 진단. 첫 도구 호출에서 401로 늦게 터지기 전에 setup 직후 확인용.',
|
|
30
|
+
'매출 리포트 접근 가능 여부도 함께 확인한다 — 그쪽만 요구 롤이 달라서',
|
|
31
|
+
'(Admin/Finance/Sales and Reports) 앱 메타데이터는 다 되는데 매출만 403 인 키가 흔하다.',
|
|
32
|
+
'인자 없음.',
|
|
33
|
+
].join(' '), {}, async () => {
|
|
28
34
|
const r = await appstore.verifyAppStoreCredentials();
|
|
29
35
|
if (r.ok) {
|
|
36
|
+
// 키가 유효할 때만 물어본다 — 인증 자체가 깨졌으면 403/401 구분이 무의미하다.
|
|
37
|
+
const reports = await appstoreSales.probeReportsAccess();
|
|
38
|
+
const reportsIcon = reports.status === 'ok' ? '✓' : reports.status === 'forbidden' ? '✗' : '·';
|
|
30
39
|
return {
|
|
31
40
|
content: [{
|
|
32
41
|
type: 'text',
|
|
@@ -34,6 +43,10 @@ export function registerAppstoreTools(server) {
|
|
|
34
43
|
'✓ App Store Connect 인증 유효',
|
|
35
44
|
r.appCount != null ? ` 접근 가능 앱: ${r.appCount}개` : '',
|
|
36
45
|
r.firstApp ? ` 예: ${r.firstApp.name ?? r.firstApp.id}` : '',
|
|
46
|
+
`${reportsIcon} 매출 리포트: ${reports.detail}`,
|
|
47
|
+
reports.status === 'forbidden'
|
|
48
|
+
? ' → 키 롤은 나중에 못 바꾸는 경우가 있다. 그때는 위 롤로 새 키를 발급해 다시 등록할 것.'
|
|
49
|
+
: '',
|
|
37
50
|
].filter(Boolean).join('\n'),
|
|
38
51
|
}],
|
|
39
52
|
};
|
|
@@ -1504,6 +1517,9 @@ export function registerAppstoreTools(server) {
|
|
|
1504
1517
|
'⚠️ 데이터가 없는 날짜는 Apple 이 404 를 주므로 datesWithoutData 로 따로 돌려준다 —',
|
|
1505
1518
|
'"매출 0" 과 "리포트 미생성/설정 오류"를 섞지 말 것. 당일치는 보통 아직 없다.',
|
|
1506
1519
|
'vendorNumber 는 ~/.mimi-seed/appstore.json 에 저장해두면 생략 가능.',
|
|
1520
|
+
'⚠️ **리포트는 요구 롤이 다르다** — Admin/Finance/Sales and Reports 중 하나여야 하고,',
|
|
1521
|
+
'배포에 흔히 쓰는 App Manager 키는 여기서만 403 이 난다. 발급된 키의 롤은 수정할 수 없으므로,',
|
|
1522
|
+
'읽기 전용 Finance 키를 발급해 appstore.json 의 **reportsKey** 에 넣으면 배포 키를 건드리지 않아도 된다.',
|
|
1507
1523
|
].join(' '), {
|
|
1508
1524
|
startDate: z.string().describe('시작일 YYYY-MM-DD (DAILY 가 아니면 이 값이 곧 reportDate)'),
|
|
1509
1525
|
endDate: z
|
package/package.json
CHANGED