@yoonion/mimi-seed-mcp 0.1.0 → 0.2.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/README.md CHANGED
@@ -48,9 +48,34 @@ npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth
48
48
  | Firebase | `firebase_list_projects` / `firebase_create_android_app` / `firebase_get_android_config` / `firebase_enable_service` |
49
49
  | AdMob | `admob_list_apps` / `admob_create_ad_unit` / `admob_get_today_earnings` / `admob_get_report` |
50
50
  | Google Play | `playstore_list_tracks` / `playstore_list_subscriptions` / `playstore_reply_review` / **`playstore_verify_service_account`** |
51
+ | Google Cloud IAM | `iam_list_service_accounts` / `iam_create_service_account` / `iam_list_keys` / **`iam_create_key`** / `iam_add_iam_policy_binding` |
51
52
  | App Store Connect | `appstore_list_apps` / `appstore_list_builds` / `appstore_list_beta_groups` |
52
53
  | 상태 | `mimi_seed_auth_status` |
53
54
 
55
+ ## End-to-end: 서비스 계정 → JSON 키 → Play Console 권한
56
+
57
+ 서버(예: [onesub](https://github.com/jeonghwanko/onesub))가 Google Play 영수증을 백그라운드로 검증하려면 서비스 계정 JSON이 필요합니다. Claude에게 한 번에 시킬 수 있어요:
58
+
59
+ > 1. `my-project`에 `onesub-play-verifier` 서비스 계정 만들고
60
+ > 2. JSON 키 발급받아서
61
+ > 3. `com.yourapp.id`에 대해 검증
62
+
63
+ Claude가 연쇄 호출:
64
+
65
+ - `iam_create_service_account("my-project", "onesub-play-verifier", "onesub Play verifier")`
66
+ - `iam_create_key("onesub-play-verifier@my-project.iam.gserviceaccount.com")` → JSON 반환
67
+ - `playstore_verify_service_account(<json>, "com.yourapp.id")` → 아직 Play Console 권한이 없어서 403 반환 기대
68
+
69
+ 그 다음 **Play Console에서 수동으로** (또는 별도 androidpublisher.users API 호출):
70
+
71
+ - Users and permissions → 서비스 계정 이메일 초대
72
+ - **View financial data, orders, and cancellation survey responses** 체크
73
+ - **~5분 대기** 후 `playstore_verify_service_account` 재실행 → ✓
74
+
75
+ 마지막으로 JSON을 `GOOGLE_SERVICE_ACCOUNT_KEY` 서버 env에 넣으면 Play 영수증 검증 가능.
76
+
77
+ > Cloud IAM 역할과 Play Console 권한은 다릅니다. `iam_add_iam_policy_binding`은 Cloud IAM 역할(예: `roles/iam.serviceAccountTokenCreator`)만 부여 — Play Console의 "View financial data"는 별도.
78
+
54
79
  ## `playstore_verify_service_account` — 서비스 계정 JSON 검증
55
80
 
56
81
  [onesub](https://github.com/jeonghwanko/onesub) 같은 서버가 Google Play 영수증을 백그라운드로 검증하려면 OAuth가 아니라 **서비스 계정 JSON**이 필요. 이 도구는 JSON을 붙여넣으면 단계별로 확인:
@@ -0,0 +1,45 @@
1
+ import type { OAuth2Client } from 'google-auth-library';
2
+ export declare function listServiceAccounts(auth: OAuth2Client, projectId: string): Promise<{
3
+ email: string | null | undefined;
4
+ displayName: string | null | undefined;
5
+ uniqueId: string | null | undefined;
6
+ disabled: boolean;
7
+ }[]>;
8
+ export declare function createServiceAccount(auth: OAuth2Client, projectId: string, accountId: string, displayName: string): Promise<{
9
+ email: string | null | undefined;
10
+ uniqueId: string | null | undefined;
11
+ displayName: string | null | undefined;
12
+ projectId: string | null | undefined;
13
+ }>;
14
+ export declare function createServiceAccountKey(auth: OAuth2Client, serviceAccountEmail: string): Promise<{
15
+ keyId: string | null;
16
+ clientEmail: string | undefined;
17
+ projectId: string | undefined;
18
+ json: string;
19
+ }>;
20
+ export declare function listServiceAccountKeys(auth: OAuth2Client, serviceAccountEmail: string): Promise<{
21
+ id: string | null;
22
+ keyType: string | null | undefined;
23
+ validBeforeTime: string | null | undefined;
24
+ validAfterTime: string | null | undefined;
25
+ }[]>;
26
+ /**
27
+ * 프로젝트의 IAM 정책을 read-modify-write 해서 member에게 role 부여.
28
+ * 이미 같은 (role, member) 바인딩이 있으면 no-op.
29
+ *
30
+ * member 예:
31
+ * 'serviceAccount:my-sa@my-project.iam.gserviceaccount.com'
32
+ * 'user:alice@example.com'
33
+ * 'group:admins@example.com'
34
+ *
35
+ * 자주 쓰는 role 예:
36
+ * 'roles/iam.serviceAccountUser' — 서비스 계정으로 위임 가능
37
+ * 'roles/iam.serviceAccountTokenCreator' — 서비스 계정의 단기 토큰 발급
38
+ * 'roles/editor' / 'roles/owner' — 광범위 (조심)
39
+ */
40
+ export declare function addProjectIamPolicyBinding(auth: OAuth2Client, projectId: string, member: string, role: string): Promise<{
41
+ added: boolean;
42
+ role: string;
43
+ member: string;
44
+ etag: string | null | undefined;
45
+ }>;
@@ -0,0 +1,126 @@
1
+ import { google } from 'googleapis';
2
+ /**
3
+ * Google Cloud IAM + Cloud Resource Manager 래퍼.
4
+ *
5
+ * 사용자가 mimi-seed-mcp로 "서비스 계정 만들기 → JSON 키 발급 → Cloud IAM
6
+ * 역할 부여"를 자동화할 때 쓰는 도구들. (Play Console의 'View financial
7
+ * data' 권한은 여기서 부여하는 Cloud IAM 역할과는 별개 — 그건 Play Console
8
+ * Users and permissions에서 androidpublisher.users API 또는 수동으로.)
9
+ */
10
+ const iam = () => google.iam('v1');
11
+ const crm = () => google.cloudresourcemanager('v1');
12
+ // ─── 서비스 계정 조회 ───
13
+ export async function listServiceAccounts(auth, projectId) {
14
+ const res = await iam().projects.serviceAccounts.list({
15
+ auth,
16
+ name: `projects/${projectId}`,
17
+ pageSize: 100,
18
+ });
19
+ return (res.data.accounts ?? []).map((a) => ({
20
+ email: a.email,
21
+ displayName: a.displayName,
22
+ uniqueId: a.uniqueId,
23
+ disabled: a.disabled ?? false,
24
+ }));
25
+ }
26
+ // ─── 서비스 계정 생성 ───
27
+ export async function createServiceAccount(auth, projectId, accountId, displayName) {
28
+ const res = await iam().projects.serviceAccounts.create({
29
+ auth,
30
+ name: `projects/${projectId}`,
31
+ requestBody: {
32
+ accountId,
33
+ serviceAccount: { displayName },
34
+ },
35
+ });
36
+ return {
37
+ email: res.data.email,
38
+ uniqueId: res.data.uniqueId,
39
+ displayName: res.data.displayName,
40
+ projectId: res.data.projectId,
41
+ };
42
+ }
43
+ // ─── 키 발급 (JSON 다운로드) ───
44
+ export async function createServiceAccountKey(auth, serviceAccountEmail) {
45
+ const res = await iam().projects.serviceAccounts.keys.create({
46
+ auth,
47
+ name: `projects/-/serviceAccounts/${serviceAccountEmail}`,
48
+ requestBody: {
49
+ keyAlgorithm: 'KEY_ALG_RSA_2048',
50
+ privateKeyType: 'TYPE_GOOGLE_CREDENTIALS_FILE',
51
+ },
52
+ });
53
+ // privateKeyData는 base64로 인코딩된 JSON 전체 — 디코딩해서 반환.
54
+ // 주의: 이 JSON은 영구 자격증명이므로 절대 로그에 남기거나 원격으로 보내면 안 됨.
55
+ const privateKeyData = res.data.privateKeyData;
56
+ if (!privateKeyData)
57
+ throw new Error('No privateKeyData in response');
58
+ const jsonString = Buffer.from(privateKeyData, 'base64').toString('utf-8');
59
+ // 파싱해서 필드 일부만 미리보기로 노출 + 전체 JSON도 함께 반환
60
+ const parsed = JSON.parse(jsonString);
61
+ return {
62
+ keyId: res.data.name?.split('/').pop() ?? null,
63
+ clientEmail: parsed.client_email,
64
+ projectId: parsed.project_id,
65
+ json: jsonString,
66
+ };
67
+ }
68
+ // ─── 키 목록 ───
69
+ export async function listServiceAccountKeys(auth, serviceAccountEmail) {
70
+ const res = await iam().projects.serviceAccounts.keys.list({
71
+ auth,
72
+ name: `projects/-/serviceAccounts/${serviceAccountEmail}`,
73
+ });
74
+ return (res.data.keys ?? []).map((k) => ({
75
+ id: k.name?.split('/').pop() ?? null,
76
+ keyType: k.keyType, // 'USER_MANAGED' | 'SYSTEM_MANAGED'
77
+ validBeforeTime: k.validBeforeTime,
78
+ validAfterTime: k.validAfterTime,
79
+ }));
80
+ }
81
+ // ─── 프로젝트 IAM 정책에 역할 바인딩 추가 ───
82
+ /**
83
+ * 프로젝트의 IAM 정책을 read-modify-write 해서 member에게 role 부여.
84
+ * 이미 같은 (role, member) 바인딩이 있으면 no-op.
85
+ *
86
+ * member 예:
87
+ * 'serviceAccount:my-sa@my-project.iam.gserviceaccount.com'
88
+ * 'user:alice@example.com'
89
+ * 'group:admins@example.com'
90
+ *
91
+ * 자주 쓰는 role 예:
92
+ * 'roles/iam.serviceAccountUser' — 서비스 계정으로 위임 가능
93
+ * 'roles/iam.serviceAccountTokenCreator' — 서비스 계정의 단기 토큰 발급
94
+ * 'roles/editor' / 'roles/owner' — 광범위 (조심)
95
+ */
96
+ export async function addProjectIamPolicyBinding(auth, projectId, member, role) {
97
+ const current = await crm().projects.getIamPolicy({
98
+ auth,
99
+ resource: projectId,
100
+ requestBody: {},
101
+ });
102
+ const policy = current.data;
103
+ const bindings = policy.bindings ?? [];
104
+ const existing = bindings.find((b) => b.role === role);
105
+ let added = false;
106
+ if (existing) {
107
+ const members = existing.members ?? [];
108
+ if (!members.includes(member)) {
109
+ existing.members = [...members, member];
110
+ added = true;
111
+ }
112
+ }
113
+ else {
114
+ bindings.push({ role, members: [member] });
115
+ added = true;
116
+ }
117
+ if (!added) {
118
+ return { added: false, role, member, etag: policy.etag };
119
+ }
120
+ const updated = await crm().projects.setIamPolicy({
121
+ auth,
122
+ resource: projectId,
123
+ requestBody: { policy: { bindings, etag: policy.etag } },
124
+ });
125
+ return { added: true, role, member, etag: updated.data.etag };
126
+ }
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import * as firebase from './firebase/tools.js';
7
7
  import * as admob from './admob/tools.js';
8
8
  import * as playstore from './playstore/tools.js';
9
9
  import * as appstore from './appstore/tools.js';
10
+ import * as iam from './iam/tools.js';
10
11
  const server = new McpServer({
11
12
  name: 'mimi-seed',
12
13
  version: '0.1.0',
@@ -374,6 +375,113 @@ server.tool('playstore_verify_service_account', [
374
375
  return { content: [{ type: 'text', text: lines.join('\n') }] };
375
376
  });
376
377
  // ══════════════════════════════════════════════════
378
+ // Google Cloud IAM Tools
379
+ // ══════════════════════════════════════════════════
380
+ // 서비스 계정 생성 → JSON 키 발급 → 프로젝트 IAM 역할 부여까지 자동화.
381
+ // Play Console의 'View financial data' 권한은 Cloud IAM이 아니라 Play Console
382
+ // Users and permissions에서 별도로 부여해야 함 (androidpublisher.users API
383
+ // 또는 수동 UI).
384
+ server.tool('iam_list_service_accounts', '주어진 projectId의 서비스 계정 목록. 이메일 / displayName / disabled 상태 반환.', {
385
+ projectId: z.string().describe('Google Cloud 프로젝트 ID'),
386
+ }, async ({ projectId }) => {
387
+ const auth = requireAuth();
388
+ const accounts = await iam.listServiceAccounts(auth, projectId);
389
+ return { content: [{ type: 'text', text: JSON.stringify(accounts, null, 2) }] };
390
+ });
391
+ server.tool('iam_create_service_account', '새 서비스 계정 생성. accountId는 이메일의 로컬 파트 (예: "onesub-play-verifier" → onesub-play-verifier@<project>.iam.gserviceaccount.com).', {
392
+ projectId: z.string().describe('Google Cloud 프로젝트 ID'),
393
+ accountId: z
394
+ .string()
395
+ .regex(/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/, 'lowercase letters, digits, hyphens; 6-30 chars')
396
+ .describe('서비스 계정 ID (이메일 로컬 파트)'),
397
+ displayName: z.string().describe('사람이 읽을 표시 이름 (예: "onesub Play verifier")'),
398
+ }, async ({ projectId, accountId, displayName }) => {
399
+ const auth = requireAuth();
400
+ const account = await iam.createServiceAccount(auth, projectId, accountId, displayName);
401
+ return {
402
+ content: [
403
+ {
404
+ type: 'text',
405
+ text: [
406
+ '✓ 서비스 계정 생성 완료',
407
+ '',
408
+ `**email**: \`${account.email}\``,
409
+ `**displayName**: ${account.displayName}`,
410
+ `**uniqueId**: \`${account.uniqueId}\``,
411
+ '',
412
+ '다음 단계:',
413
+ `1. \`iam_create_key("${account.email}")\` — JSON 키 발급`,
414
+ `2. \`playstore_verify_service_account\` 로 Play Console 권한까지 확인 (Play Console에서 수동으로 이메일 초대 + 'View financial data' 권한 부여는 별도)`,
415
+ ].join('\n'),
416
+ },
417
+ ],
418
+ };
419
+ });
420
+ server.tool('iam_list_keys', '주어진 서비스 계정의 기존 키 목록. keyId / keyType / 만료시간 반환. 회수할 키 찾을 때 사용.', {
421
+ serviceAccount: z.string().describe('서비스 계정 이메일'),
422
+ }, async ({ serviceAccount }) => {
423
+ const auth = requireAuth();
424
+ const keys = await iam.listServiceAccountKeys(auth, serviceAccount);
425
+ return { content: [{ type: 'text', text: JSON.stringify(keys, null, 2) }] };
426
+ });
427
+ server.tool('iam_create_key', [
428
+ '주어진 서비스 계정의 새 JSON 키를 발급. 응답에 전체 JSON 포함 — 그대로 onesub의 GOOGLE_SERVICE_ACCOUNT_KEY 환경변수로 쓸 수 있음.',
429
+ '주의: 반환된 JSON은 영구 자격증명이므로 안전하게 보관. 한 서비스 계정당 키 최대 10개 (기존 키 회수 후 발급 필요하면 iam_list_keys로 먼저 확인).',
430
+ ].join(' '), {
431
+ serviceAccount: z.string().describe('서비스 계정 이메일'),
432
+ }, async ({ serviceAccount }) => {
433
+ const auth = requireAuth();
434
+ const key = await iam.createServiceAccountKey(auth, serviceAccount);
435
+ return {
436
+ content: [
437
+ {
438
+ type: 'text',
439
+ text: [
440
+ '✓ 새 키 발급 완료 — 아래 JSON을 안전하게 보관하세요. **다시 볼 수 없습니다.**',
441
+ '',
442
+ `**keyId**: \`${key.keyId}\``,
443
+ `**clientEmail**: \`${key.clientEmail}\``,
444
+ `**projectId**: \`${key.projectId}\``,
445
+ '',
446
+ '## Service account JSON',
447
+ '```json',
448
+ key.json,
449
+ '```',
450
+ '',
451
+ 'onesub 서버 `GOOGLE_SERVICE_ACCOUNT_KEY` env에 한 줄로 넣을 때:',
452
+ '```bash',
453
+ "cat service-account.json | tr -d '\\n' | jq -c .",
454
+ '```',
455
+ ].join('\n'),
456
+ },
457
+ ],
458
+ };
459
+ });
460
+ server.tool('iam_add_iam_policy_binding', [
461
+ '프로젝트 IAM 정책에 (member → role) 바인딩 추가. 이미 같은 조합이 있으면 no-op.',
462
+ 'member 형식: `serviceAccount:<email>` / `user:<email>` / `group:<email>`.',
463
+ '주의: 이건 Google Cloud IAM 역할입니다. Play Console의 "View financial data" 권한은 별도 — Play Console → Users and permissions에서 부여.',
464
+ ].join(' '), {
465
+ projectId: z.string().describe('Google Cloud 프로젝트 ID'),
466
+ member: z
467
+ .string()
468
+ .describe('권한 받을 주체 (예: serviceAccount:my-sa@my-project.iam.gserviceaccount.com)'),
469
+ role: z.string().describe('IAM 역할 (예: roles/iam.serviceAccountTokenCreator)'),
470
+ }, async ({ projectId, member, role }) => {
471
+ const auth = requireAuth();
472
+ const result = await iam.addProjectIamPolicyBinding(auth, projectId, member, role);
473
+ return {
474
+ content: [
475
+ {
476
+ type: 'text',
477
+ text: result.added
478
+ ? `✓ 바인딩 추가: \`${member}\` → \`${role}\``
479
+ : `• 이미 존재: \`${member}\` → \`${role}\` (no-op)`,
480
+ },
481
+ ],
482
+ };
483
+ });
484
+ // ══════════════════════════════════════════════════
377
485
  // App Store Connect Tools
378
486
  // ══════════════════════════════════════════════════
379
487
  server.tool('appstore_list_apps', 'App Store Connect 앱 목록 조회', {}, async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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": {