@yoonion/mimi-seed-mcp 0.3.11 → 0.3.13

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.
@@ -5,5 +5,5 @@ export interface AppStoreCredentials {
5
5
  }
6
6
  export declare function getAppStoreCredentials(): AppStoreCredentials | null;
7
7
  export declare function saveAppStoreCredentials(creds: AppStoreCredentials): void;
8
- export declare function generateToken(creds: AppStoreCredentials): string;
9
- export declare function getAuthHeaders(): Record<string, string> | null;
8
+ export declare function generateToken(creds: AppStoreCredentials): Promise<string>;
9
+ export declare function getAuthHeaders(): Promise<Record<string, string> | null>;
@@ -1,4 +1,4 @@
1
- import jwt from 'jsonwebtoken';
1
+ import { SignJWT, importPKCS8 } from 'jose';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
@@ -28,26 +28,20 @@ export function saveAppStoreCredentials(creds) {
28
28
  fs.mkdirSync(dir, { recursive: true });
29
29
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(creds, null, 2), { mode: 0o600 });
30
30
  }
31
- export function generateToken(creds) {
32
- const now = Math.floor(Date.now() / 1000);
33
- return jwt.sign({
34
- iss: creds.issuerId,
35
- iat: now,
36
- exp: now + 20 * 60, // 20분
37
- aud: 'appstoreconnect-v1',
38
- }, creds.privateKey, {
39
- algorithm: 'ES256',
40
- header: {
41
- alg: 'ES256',
42
- kid: creds.keyId,
43
- typ: 'JWT',
44
- },
45
- });
31
+ export async function generateToken(creds) {
32
+ const key = await importPKCS8(creds.privateKey, 'ES256');
33
+ return new SignJWT({})
34
+ .setProtectedHeader({ alg: 'ES256', kid: creds.keyId, typ: 'JWT' })
35
+ .setIssuer(creds.issuerId)
36
+ .setIssuedAt()
37
+ .setExpirationTime('20m')
38
+ .setAudience('appstoreconnect-v1')
39
+ .sign(key);
46
40
  }
47
- export function getAuthHeaders() {
41
+ export async function getAuthHeaders() {
48
42
  const creds = getAppStoreCredentials();
49
43
  if (!creds)
50
44
  return null;
51
- const token = generateToken(creds);
45
+ const token = await generateToken(creds);
52
46
  return { Authorization: `Bearer ${token}` };
53
47
  }
@@ -14,8 +14,8 @@ import crypto from 'node:crypto';
14
14
  * 4. commit — PATCH /appScreenshots/{id} { uploaded: true, sourceFileChecksum }
15
15
  */
16
16
  const BASE = 'https://api.appstoreconnect.apple.com/v1';
17
- function authHeadersOrThrow() {
18
- const headers = getAuthHeaders();
17
+ async function authHeadersOrThrow() {
18
+ const headers = await getAuthHeaders();
19
19
  if (!headers) {
20
20
  throw new Error([
21
21
  '❌ App Store Connect 인증이 필요해.',
@@ -27,7 +27,7 @@ function authHeadersOrThrow() {
27
27
  return headers;
28
28
  }
29
29
  async function req(pathOrUrl, init = {}) {
30
- const headers = authHeadersOrThrow();
30
+ const headers = await authHeadersOrThrow();
31
31
  const url = pathOrUrl.startsWith('http') ? pathOrUrl : `${BASE}${pathOrUrl}`;
32
32
  const res = await fetch(url, {
33
33
  ...init,
@@ -2,6 +2,30 @@ export declare function apiGet(path: string, params?: Record<string, string>): P
2
2
  export declare function listApps(): Promise<any>;
3
3
  export declare function getApp(appId: string): Promise<any>;
4
4
  export declare function listVersions(appId: string): Promise<any>;
5
+ export type ApplePlatform = 'IOS' | 'MAC_OS' | 'TV_OS' | 'VISION_OS';
6
+ export type AppleReleaseType = 'MANUAL' | 'AFTER_APPROVAL' | 'SCHEDULED';
7
+ export interface CreateVersionInput {
8
+ appId: string;
9
+ versionString: string;
10
+ platform: ApplePlatform;
11
+ copyright?: string;
12
+ releaseType?: AppleReleaseType;
13
+ earliestReleaseDate?: string;
14
+ buildId?: string;
15
+ }
16
+ export declare function createVersion(input: CreateVersionInput): Promise<{
17
+ id: any;
18
+ version: any;
19
+ platform: any;
20
+ state: any;
21
+ releaseType: any;
22
+ createdDate: any;
23
+ }>;
24
+ export declare function attachBuildToVersion(versionId: string, buildId: string): Promise<{
25
+ versionId: string;
26
+ buildId: string;
27
+ ok: boolean;
28
+ }>;
5
29
  export declare function getVersionLocalizations(versionId: string): Promise<any>;
6
30
  export interface LocalizationUpdateFields {
7
31
  whatsNew?: string;
@@ -5,7 +5,7 @@ import { getAuthHeaders } from './auth.js';
5
5
  */
6
6
  const BASE = 'https://api.appstoreconnect.apple.com/v1';
7
7
  export async function apiGet(path, params) {
8
- const headers = getAuthHeaders();
8
+ const headers = await getAuthHeaders();
9
9
  if (!headers)
10
10
  throw new Error([
11
11
  '❌ App Store Connect 인증이 필요해.',
@@ -29,7 +29,7 @@ export async function apiGet(path, params) {
29
29
  return res.json();
30
30
  }
31
31
  async function apiPatch(path, body) {
32
- const headers = getAuthHeaders();
32
+ const headers = await getAuthHeaders();
33
33
  if (!headers)
34
34
  throw new Error([
35
35
  '❌ App Store Connect 인증이 필요해.',
@@ -51,7 +51,7 @@ async function apiPatch(path, body) {
51
51
  return text ? JSON.parse(text) : { ok: true };
52
52
  }
53
53
  async function apiPost(path, body) {
54
- const headers = getAuthHeaders();
54
+ const headers = await getAuthHeaders();
55
55
  if (!headers)
56
56
  throw new Error([
57
57
  '❌ App Store Connect 인증이 필요해.',
@@ -107,6 +107,46 @@ export async function listVersions(appId) {
107
107
  createdDate: v.attributes?.createdDate,
108
108
  }));
109
109
  }
110
+ export async function createVersion(input) {
111
+ const attributes = {
112
+ platform: input.platform,
113
+ versionString: input.versionString,
114
+ };
115
+ if (input.copyright !== undefined)
116
+ attributes.copyright = input.copyright;
117
+ if (input.releaseType !== undefined)
118
+ attributes.releaseType = input.releaseType;
119
+ if (input.earliestReleaseDate !== undefined)
120
+ attributes.earliestReleaseDate = input.earliestReleaseDate;
121
+ const relationships = {
122
+ app: { data: { type: 'apps', id: input.appId } },
123
+ };
124
+ if (input.buildId) {
125
+ relationships.build = { data: { type: 'builds', id: input.buildId } };
126
+ }
127
+ const created = await apiPost('/appStoreVersions', {
128
+ data: {
129
+ type: 'appStoreVersions',
130
+ attributes,
131
+ relationships,
132
+ },
133
+ });
134
+ return {
135
+ id: created?.data?.id,
136
+ version: created?.data?.attributes?.versionString,
137
+ platform: created?.data?.attributes?.platform,
138
+ state: created?.data?.attributes?.appStoreState ?? created?.data?.attributes?.state,
139
+ releaseType: created?.data?.attributes?.releaseType,
140
+ createdDate: created?.data?.attributes?.createdDate,
141
+ };
142
+ }
143
+ export async function attachBuildToVersion(versionId, buildId) {
144
+ // /relationships/build 엔드포인트는 204 No Content 반환
145
+ await apiPatch(`/appStoreVersions/${versionId}/relationships/build`, {
146
+ data: { type: 'builds', id: buildId },
147
+ });
148
+ return { versionId, buildId, ok: true };
149
+ }
110
150
  // ─── 로컬라이제이션 (메타데이터) ───
111
151
  export async function getVersionLocalizations(versionId) {
112
152
  const data = await apiGet(`/appStoreVersions/${versionId}/appStoreVersionLocalizations`, {
@@ -381,7 +421,7 @@ export async function submitVersionForReview(versionId) {
381
421
  // 자동 제출(submission)은 권장 가이드라인 (스크린샷·리뷰 노트 등)이 충족돼야 통과.
382
422
  const IAP_V2_BASE = 'https://api.appstoreconnect.apple.com/v2';
383
423
  async function apiPostV2(path, body) {
384
- const headers = getAuthHeaders();
424
+ const headers = await getAuthHeaders();
385
425
  if (!headers) {
386
426
  throw new Error('App Store Connect 인증 필요 — npx -p @yoonion/mimi-seed-mcp mimi-seed-appstore-auth');
387
427
  }
package/dist/index.js CHANGED
@@ -705,6 +705,69 @@ server.tool('appstore_list_versions', 'App Store 버전 목록 (심사 상태
705
705
  const versions = await appstore.listVersions(appId);
706
706
  return { content: [{ type: 'text', text: JSON.stringify(versions, null, 2) }] };
707
707
  });
708
+ server.tool('appstore_create_version', [
709
+ 'App Store 새 버전 레코드 생성 — POST /v1/appStoreVersions.',
710
+ '새 versionString(예: "1.2.3")으로 PREPARE_FOR_SUBMISSION 상태의 버전을 만듦.',
711
+ 'buildId를 함께 주면 생성과 동시에 빌드 연결. 나중에 붙이려면 appstore_attach_build 사용.',
712
+ 'releaseType: MANUAL(개발자가 출시) / AFTER_APPROVAL(승인 후 자동) / SCHEDULED(earliestReleaseDate 필요).',
713
+ ].join(' '), {
714
+ appId: z.string().describe('App Store 앱 ID (appstore_list_apps 결과의 id, 숫자형)'),
715
+ versionString: z.string().describe('버전 문자열 (예: "1.2.3")'),
716
+ platform: z
717
+ .enum(['IOS', 'MAC_OS', 'TV_OS', 'VISION_OS'])
718
+ .default('IOS')
719
+ .describe('플랫폼 (기본 IOS)'),
720
+ copyright: z.string().optional().describe('저작권 표기 (예: "© 2026 Foo Inc.")'),
721
+ releaseType: z
722
+ .enum(['MANUAL', 'AFTER_APPROVAL', 'SCHEDULED'])
723
+ .optional()
724
+ .describe('출시 방식 (생략 시 Apple 기본값)'),
725
+ earliestReleaseDate: z
726
+ .string()
727
+ .optional()
728
+ .describe('SCHEDULED일 때 가장 빠른 출시 시각 (ISO 8601, 예: "2026-05-01T00:00:00Z")'),
729
+ buildId: z
730
+ .string()
731
+ .optional()
732
+ .describe('연결할 빌드 ID (appstore_list_builds 결과). 생략 시 버전만 생성하고 나중에 attach.'),
733
+ }, async ({ appId, versionString, platform, copyright, releaseType, earliestReleaseDate, buildId }) => {
734
+ const result = await appstore.createVersion({
735
+ appId,
736
+ versionString,
737
+ platform,
738
+ copyright,
739
+ releaseType,
740
+ earliestReleaseDate,
741
+ buildId,
742
+ });
743
+ return {
744
+ content: [
745
+ {
746
+ type: 'text',
747
+ text: `✅ 버전 ${versionString} (${platform}) 생성됨${buildId ? ` + 빌드 ${buildId} 연결됨` : ''}.\n\n${JSON.stringify(result, null, 2)}`,
748
+ },
749
+ ],
750
+ };
751
+ });
752
+ server.tool('appstore_attach_build', [
753
+ 'App Store 버전에 업로드된 빌드를 연결 — PATCH /v1/appStoreVersions/{id}/relationships/build.',
754
+ 'TestFlight에 업로드되어 processingState=VALID 상태인 빌드만 연결 가능.',
755
+ '편집 가능한 버전(PREPARE_FOR_SUBMISSION 등)에서만 변경됨.',
756
+ 'buildId는 appstore_list_builds 결과 사용.',
757
+ ].join(' '), {
758
+ versionId: z.string().describe('App Store 버전 ID (appstore_list_versions 또는 appstore_create_version 결과)'),
759
+ buildId: z.string().describe('빌드 ID (appstore_list_builds 결과)'),
760
+ }, async ({ versionId, buildId }) => {
761
+ const result = await appstore.attachBuildToVersion(versionId, buildId);
762
+ return {
763
+ content: [
764
+ {
765
+ type: 'text',
766
+ text: `✅ 빌드 ${buildId}가 버전 ${versionId}에 연결됐어.\n\n${JSON.stringify(result, null, 2)}`,
767
+ },
768
+ ],
769
+ };
770
+ });
708
771
  server.tool('appstore_get_metadata', 'App Store 버전 메타데이터 (설명문, 키워드, What\'s New)', { versionId: z.string().describe('버전 ID') }, async ({ versionId }) => {
709
772
  const localizations = await appstore.getVersionLocalizations(versionId);
710
773
  return { content: [{ type: 'text', text: JSON.stringify(localizations, null, 2) }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
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": {
@@ -47,12 +47,11 @@
47
47
  "@modelcontextprotocol/sdk": "^1.12.1",
48
48
  "@onesub/providers": "^0.2.0",
49
49
  "googleapis": "^171.4.0",
50
- "jsonwebtoken": "^9.0.3",
50
+ "jose": "^5.10.0",
51
51
  "open": "^10.1.0",
52
52
  "zod": "^3.24.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@types/jsonwebtoken": "^9.0.10",
56
55
  "@types/node": "^22.0.0",
57
56
  "tsx": "^4.19.0",
58
57
  "typescript": "^5.7.0"