@yoonion/mimi-seed-mcp 0.9.1 → 0.9.2

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
@@ -93,7 +93,7 @@ export ANTHROPIC_API_KEY=sk-ant-...
93
93
  | 점검 / 위험 | 4 | `playstore_check_submission_risks` / `appstore_check_submission_risks` / `screenshot_validate` / `release_status` |
94
94
  | Instagram | 4 | `instagram_post_image` / `instagram_post_carousel` / `instagram_save_config` |
95
95
  | Android 서명 | 3 | `android_signing_setup` / `android_generate_keystore` / `jenkins_upload_playstore_sa` |
96
- | 인증 | 3 | `mimi_seed_status` / `mimi_seed_auth_start` / `mimi_seed_auth_status` |
96
+ | 인증 | 4 | `mimi_seed_status` / `mimi_seed_auth_start` / `mimi_seed_auth_status` / `mimi_seed_remote_sync_credentials` |
97
97
  | AI (Claude) | 2 | `generate_release_notes_from_commits` / `generate_review_reply` |
98
98
 
99
99
  > 인앱 결제(IAP·구독) 도구는 위 Play Store·App Store 카운트에 포함됩니다 — `appstore_create_inapp_purchase` · `appstore_update_product_review_note` · `appstore_upload_product_review_screenshot` 등.
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from 'node:fs';
2
+ import { z } from 'zod';
2
3
  import { getMcpOAuthClient } from '../auth/constants.js';
3
4
  import { classifyError } from '../auth/errors.js';
4
5
  import { startAuth, ensureFreshAccessToken, getTokensLastRefreshMs } from '../auth/google-auth.js';
@@ -10,6 +11,7 @@ import { loadConfig as loadGoogleAdsConfig } from '../googleads/config.js';
10
11
  import { loadFacebookConfig } from '../facebook/config.js';
11
12
  import { loadInstagramConfig } from '../instagram/config.js';
12
13
  import { resolveBigQueryAuth } from '../auth/bigquery-auth.js';
14
+ import { syncRemoteCredentials } from '../remote-sync.js';
13
15
  import { findProjectManifest, manifestServiceEntries, } from '../lib/project-manifest.js';
14
16
  /**
15
17
  * tokens.json mtime → "Nd Hh ago" 형식 문자열 + 재인증 권고.
@@ -322,4 +324,27 @@ export function registerAuthTools(server) {
322
324
  };
323
325
  }
324
326
  });
327
+ server.tool('mimi_seed_remote_sync_credentials', [
328
+ '로컬 ~/.mimi-seed의 App Store Connect 키와 패키지별 Google Play 서비스 계정을',
329
+ 'Mimi Seed 원격 MCP에 검증 후 암호화 동기화합니다.',
330
+ '기본은 미리보기이며 실제 비밀값 전송에는 confirm=true가 필요합니다.',
331
+ '로컬 Google OAuth tokens.json은 보안 및 OAuth 클라이언트 호환성 때문에 복사하지 않습니다.',
332
+ ].join(' '), {
333
+ confirm: z.boolean().optional().describe('true일 때만 원격에 비밀값을 전송하고 저장'),
334
+ include_appstore: z.boolean().optional().describe('App Store 키 포함 (기본 true)'),
335
+ include_playstore: z.boolean().optional().describe('Play 서비스 계정 포함 (기본 true)'),
336
+ package_names: z.array(z.string()).optional().describe('특정 Play 패키지만 동기화'),
337
+ }, async ({ confirm, include_appstore, include_playstore, package_names }) => ({
338
+ content: [
339
+ {
340
+ type: 'text',
341
+ text: await syncRemoteCredentials({
342
+ confirm,
343
+ includeAppStore: include_appstore,
344
+ includePlayStore: include_playstore,
345
+ packageNames: package_names,
346
+ }),
347
+ },
348
+ ],
349
+ }));
325
350
  }
@@ -0,0 +1,24 @@
1
+ import { type AppStoreCredentials } from './appstore/auth.js';
2
+ interface RemoteConfig {
3
+ token: string;
4
+ endpoint: string;
5
+ webBase: string;
6
+ }
7
+ export interface RemoteSyncOptions {
8
+ confirm?: boolean;
9
+ includeAppStore?: boolean;
10
+ includePlayStore?: boolean;
11
+ packageNames?: string[];
12
+ }
13
+ export interface RemoteSyncDependencies {
14
+ getConfig(): RemoteConfig | null;
15
+ getAppStoreCredentials(): AppStoreCredentials | null;
16
+ listPackageNames(): string[];
17
+ getServiceAccountJson(packageName: string): string | null;
18
+ callRemote(config: RemoteConfig, tool: string, args: Record<string, unknown>): Promise<{
19
+ text: string;
20
+ isError: boolean;
21
+ }>;
22
+ }
23
+ export declare function syncRemoteCredentials(options: RemoteSyncOptions, dependencies?: RemoteSyncDependencies): Promise<string>;
24
+ export {};
@@ -0,0 +1,135 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { getAppStoreCredentials } from './appstore/auth.js';
5
+ import { getServiceAccountJson, listRegisteredServiceAccounts, } from './auth/playstore-auth.js';
6
+ function getRemoteConfig() {
7
+ const envToken = process.env.MIMI_SEED_TOKEN;
8
+ if (envToken) {
9
+ const webBase = process.env.MIMI_SEED_WEB_BASE ?? 'https://mimi-seed.pryzm.gg';
10
+ return { token: envToken, endpoint: `${webBase}/api/mcp`, webBase };
11
+ }
12
+ try {
13
+ const configPath = path.join(os.homedir(), '.mimi-seed', 'config.json');
14
+ const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
15
+ if (!parsed.token || !parsed.endpoint)
16
+ return null;
17
+ const webBase = parsed.webBase ?? parsed.endpoint.replace(/\/api\/mcp\/?$/, '');
18
+ return { token: parsed.token, endpoint: parsed.endpoint, webBase };
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ async function callRemote(config, tool, args) {
25
+ let response;
26
+ try {
27
+ response = await fetch(config.endpoint, {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ Accept: 'application/json, text/event-stream',
32
+ Authorization: `Bearer ${config.token}`,
33
+ },
34
+ body: JSON.stringify({
35
+ jsonrpc: '2.0',
36
+ id: 1,
37
+ method: 'tools/call',
38
+ params: { name: tool, arguments: args },
39
+ }),
40
+ signal: AbortSignal.timeout(30_000),
41
+ });
42
+ }
43
+ catch {
44
+ return { text: '원격 MCP 네트워크 연결 실패', isError: true };
45
+ }
46
+ if (!response.ok) {
47
+ return { text: `원격 MCP HTTP ${response.status}`, isError: true };
48
+ }
49
+ const raw = await response.text();
50
+ let payloadRaw = raw;
51
+ if ((response.headers.get('content-type') ?? '').includes('text/event-stream')) {
52
+ const data = raw
53
+ .split('\n')
54
+ .map((line) => line.trim())
55
+ .find((line) => line.startsWith('data:'));
56
+ if (!data)
57
+ return { text: '원격 MCP 응답을 읽지 못했습니다.', isError: true };
58
+ payloadRaw = data.slice(5).trim();
59
+ }
60
+ try {
61
+ const payload = JSON.parse(payloadRaw);
62
+ if (payload.error)
63
+ return { text: payload.error.message ?? '원격 MCP 오류', isError: true };
64
+ return {
65
+ text: (payload.result?.content ?? [])
66
+ .filter((item) => item.type === 'text')
67
+ .map((item) => item.text ?? '')
68
+ .join('\n'),
69
+ isError: payload.result?.isError === true,
70
+ };
71
+ }
72
+ catch {
73
+ return { text: '원격 MCP 응답 형식이 올바르지 않습니다.', isError: true };
74
+ }
75
+ }
76
+ const defaultDependencies = {
77
+ getConfig: getRemoteConfig,
78
+ getAppStoreCredentials,
79
+ listPackageNames: () => listRegisteredServiceAccounts().perPackage.map((entry) => entry.packageName),
80
+ getServiceAccountJson: (packageName) => getServiceAccountJson(packageName),
81
+ callRemote,
82
+ };
83
+ export async function syncRemoteCredentials(options, dependencies = defaultDependencies) {
84
+ const includeAppStore = options.includeAppStore !== false;
85
+ const includePlayStore = options.includePlayStore !== false;
86
+ const appStore = includeAppStore ? dependencies.getAppStoreCredentials() : null;
87
+ const requestedPackages = options.packageNames?.map((name) => name.trim()).filter(Boolean);
88
+ const packageNames = includePlayStore
89
+ ? [...new Set(requestedPackages?.length ? requestedPackages : dependencies.listPackageNames())]
90
+ : [];
91
+ const playCredentials = packageNames
92
+ .map((packageName) => ({ packageName, json: dependencies.getServiceAccountJson(packageName) }))
93
+ .filter((entry) => Boolean(entry.json));
94
+ const lines = [
95
+ 'Mimi Seed 로컬 → 원격 자격증명 동기화',
96
+ `- App Store Connect: ${appStore ? '대상 1개' : includeAppStore ? '로컬 키 없음' : '제외'}`,
97
+ `- Google Play: ${playCredentials.length}개 패키지${packageNames.length > playCredentials.length ? ` (자격증명 없는 대상 ${packageNames.length - playCredentials.length}개)` : ''}`,
98
+ ...playCredentials.map((entry) => ` - ${entry.packageName}`),
99
+ '- Google OAuth: 복사하지 않음 (원격 웹에서 별도 동의 필요)',
100
+ ];
101
+ if (!options.confirm) {
102
+ return [...lines, '', '미리보기만 수행했습니다. 원격 저장은 confirm=true일 때만 실행됩니다.'].join('\n');
103
+ }
104
+ const config = dependencies.getConfig();
105
+ if (!config) {
106
+ return [...lines, '', '원격 연결 정보가 없습니다. 먼저 `mimi-seed init`을 실행하세요.'].join('\n');
107
+ }
108
+ const results = [];
109
+ if (appStore) {
110
+ const result = await dependencies.callRemote(config, 'import_appstore_credentials', {
111
+ key_id: appStore.keyId,
112
+ issuer_id: appStore.issuerId,
113
+ private_key: appStore.privateKey,
114
+ confirm: true,
115
+ });
116
+ results.push(`- App Store: ${result.isError ? '실패' : result.text}`);
117
+ }
118
+ for (const entry of playCredentials) {
119
+ const result = await dependencies.callRemote(config, 'import_playstore_service_account', {
120
+ package_name: entry.packageName,
121
+ service_account_json: entry.json,
122
+ confirm: true,
123
+ });
124
+ results.push(`- Play ${entry.packageName}: ${result.isError ? '실패' : result.text}`);
125
+ }
126
+ if (results.length === 0)
127
+ results.push('- 동기화할 로컬 자격증명이 없습니다.');
128
+ return [
129
+ ...lines,
130
+ '',
131
+ ...results,
132
+ '',
133
+ `Google 사용자 권한(Firebase/AdMob/vitals): ${config.webBase}/apps 에서 "Google 플랫폼 연결" 동의 필요`,
134
+ ].join('\n');
135
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
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": {