@yoonion/mimi-seed-mcp 0.6.6 → 0.8.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.
@@ -121,7 +121,9 @@ export function registerAuthTools(server) {
121
121
  lines.push(`✅ Play Store SA — ${saCount}개 등록 (${detail})`);
122
122
  }
123
123
  else {
124
- lines.push('❌ Play Store SA 미설정 playstore_register_service_account 또는 setup_playstore_connection');
124
+ // SA CI/헤드리스 전용 로컬은 OAuth(androidpublisher scope)로 대부분의 Play 작업이 된다.
125
+ // (helpers.ts requirePlayStoreAuth 가 SA → OAuth 로 폴백한다.) "필수"로 표기하면 안 된다.
126
+ lines.push('⚠️ Play Store SA — 미설정 (선택 — 로컬은 OAuth 로 가능, CI/헤드리스는 필수) → setup_playstore_connection');
125
127
  }
126
128
  // 3. App Store Connect
127
129
  const asc = getAppStoreCredentials();
@@ -188,12 +190,11 @@ export function registerAuthTools(server) {
188
190
  if (oauthResult.status !== 'fresh' && oauthResult.status !== 'refreshed') {
189
191
  missing.push(' 1. mimi_seed_auth_start (Google 계정 로그인 — Firebase/AdMob/GSC 등 필수)');
190
192
  }
191
- if (saCount === 0) {
192
- missing.push(' 2. setup_playstore_connection(packageName=..., projectId=...) (Play Store 배포 필수)');
193
- }
194
193
  if (!asc) {
195
- missing.push(' 3. npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth (App Store 배포 필수)');
194
+ missing.push(' 2. npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth (App Store 배포 필수)');
196
195
  }
196
+ // Play SA 는 "다음 단계 (필수)" 에 넣지 않는다 — OAuth 로 대부분의 Play 작업이 되므로.
197
+ // CI/헤드리스에서만 필요하고, 그 안내는 위 상태 줄(⚠️)에 이미 있다.
197
198
  if (missing.length > 0) {
198
199
  lines.push('', '── 다음 단계 (필수) ─────────────────────────────', ...missing);
199
200
  }
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { loadFacebookConfig, requireFacebookConfig, saveFacebookConfig } from '../facebook/config.js';
2
+ import { loadFacebookConfig, requireFacebookConfig } from '../facebook/config.js';
3
+ import { connectFacebook } from '../facebook/setup.js';
3
4
  import * as api from '../facebook/api.js';
4
5
  export function registerFacebookTools(server) {
5
6
  server.tool('facebook_save_config', [
@@ -11,80 +12,9 @@ export function registerFacebookTools(server) {
11
12
  pageAccessToken: z.string().describe('Facebook Page Access Token (EAA..., long-lived 권장)'),
12
13
  pageId: z.string().optional().describe('Facebook Page ID (생략 시 토큰에서 자동 조회)'),
13
14
  }, async ({ pageAccessToken, pageId }) => {
14
- // Resolve pageId
15
- let resolvedPageId = pageId;
16
- if (!resolvedPageId) {
17
- try {
18
- const me = await api.listAccessiblePages(pageAccessToken);
19
- if (me.length === 0) {
20
- // Try /me directly (already a Page Access Token)
21
- const res = await fetch(`https://graph.facebook.com/v21.0/me?fields=id,name&access_token=${pageAccessToken}`);
22
- const data = await res.json();
23
- if (data.error)
24
- throw new Error(`${data.error.message} (code ${data.error.code})`);
25
- resolvedPageId = data.id;
26
- }
27
- else if (me.length === 1) {
28
- resolvedPageId = me[0].id;
29
- }
30
- else {
31
- const list = me.map(p => ` • ${p.name} (${p.id})`).join('\n');
32
- return {
33
- content: [{
34
- type: 'text',
35
- text: [
36
- '여러 페이지에 접근 가능합니다. pageId를 명시해주세요:',
37
- list,
38
- ].join('\n'),
39
- }],
40
- };
41
- }
42
- }
43
- catch (err) {
44
- return {
45
- content: [{
46
- type: 'text',
47
- text: `❌ pageId 자동 조회 실패: ${err.message}`,
48
- }],
49
- };
50
- }
51
- }
52
- try {
53
- const cfg = { pageAccessToken, pageId: resolvedPageId };
54
- const page = await api.getPage(cfg);
55
- saveFacebookConfig({
56
- pageAccessToken,
57
- pageId: resolvedPageId,
58
- pageName: page.name,
59
- expiresAt: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString(),
60
- });
61
- return {
62
- content: [{
63
- type: 'text',
64
- text: [
65
- `✅ Facebook 페이지 연결 완료`,
66
- ` 페이지: ${page.name}`,
67
- ` ID: ${page.id}`,
68
- page.category ? ` 카테고리: ${page.category}` : '',
69
- page.followers_count !== undefined ? ` 팔로워: ${page.followers_count.toLocaleString()}` : '',
70
- page.fan_count !== undefined ? ` 좋아요: ${page.fan_count.toLocaleString()}` : '',
71
- ].filter(Boolean).join('\n'),
72
- }],
73
- };
74
- }
75
- catch (err) {
76
- return {
77
- content: [{
78
- type: 'text',
79
- text: [
80
- `❌ 토큰 검증 실패`,
81
- ` ${err.message}`,
82
- ``,
83
- `Page Access Token을 다시 확인하거나 facebook_list_pages로 페이지 목록을 조회하세요.`,
84
- ].join('\n'),
85
- }],
86
- };
87
- }
15
+ // 구현은 facebook/setup.ts 에 있다 — mimi-seed-social-auth CLI 와 공유한다.
16
+ const result = await connectFacebook(pageAccessToken, pageId);
17
+ return { content: [{ type: 'text', text: result.text }] };
88
18
  });
89
19
  server.tool('facebook_list_pages', 'User Access Token으로 접근 가능한 Facebook 페이지 목록을 조회합니다. 페이지별 Page Access Token도 함께 반환.', {
90
20
  userAccessToken: z.string().describe('Facebook User Access Token (EAA...)'),
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { requireInstagramConfig, saveInstagramConfig } from '../instagram/config.js';
2
+ import { requireInstagramConfig } from '../instagram/config.js';
3
+ import { connectInstagram } from '../instagram/setup.js';
3
4
  import * as api from '../instagram/api.js';
4
5
  export function registerInstagramTools(server) {
5
6
  server.tool('instagram_save_config', [
@@ -14,70 +15,9 @@ export function registerInstagramTools(server) {
14
15
  userId: z.string().optional().describe('Instagram Business Account ID (생략 시 자동 조회)'),
15
16
  assumeIssuedNow: z.boolean().default(true).describe('expiresAt = 지금 + 60일 자동 계산'),
16
17
  }, async ({ accessToken, userId, assumeIssuedNow }) => {
17
- const apiType = accessToken.startsWith('IGAA') ? 'Instagram Login' : 'Facebook Login';
18
- // userId 자동 조회
19
- let resolvedUserId = userId;
20
- if (!resolvedUserId) {
21
- try {
22
- resolvedUserId = await api.fetchUserId(accessToken);
23
- }
24
- catch (err) {
25
- return {
26
- content: [{
27
- type: 'text',
28
- text: [
29
- `❌ userId 자동 조회 실패 (${apiType})`,
30
- ` ${err.message}`,
31
- ``,
32
- `userId를 명시적으로 전달하거나 토큰을 다시 확인하세요.`,
33
- ].join('\n'),
34
- }],
35
- };
36
- }
37
- }
38
- const expiresAt = assumeIssuedNow
39
- ? new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString()
40
- : undefined;
41
- // 검증 — 잘못된 토큰이면 즉시 알림
42
- try {
43
- const account = await api.getAccount({ accessToken, userId: resolvedUserId });
44
- // 검증 성공 → 최종 저장
45
- saveInstagramConfig({
46
- accessToken,
47
- userId: resolvedUserId,
48
- expiresAt,
49
- username: account.username,
50
- });
51
- return {
52
- content: [{
53
- type: 'text',
54
- text: [
55
- `✅ Instagram 연결 확인 완료 (${apiType})`,
56
- ` 계정: @${account.username}${account.name ? ` (${account.name})` : ''}`,
57
- ` ID: ${account.id}`,
58
- account.account_type ? ` 타입: ${account.account_type}` : '',
59
- account.followers_count !== undefined ? ` 팔로워: ${account.followers_count.toLocaleString()}` : '',
60
- account.media_count !== undefined ? ` 게시물: ${account.media_count}` : '',
61
- expiresAt ? ` 토큰 만료(추정): ${expiresAt.slice(0, 10)}` : '',
62
- ].filter(Boolean).join('\n'),
63
- }],
64
- };
65
- }
66
- catch (err) {
67
- // 검증 실패 — 저장은 하지 않고 사용자에게 알림
68
- return {
69
- content: [{
70
- type: 'text',
71
- text: [
72
- `❌ 토큰 검증 실패 (${apiType})`,
73
- ` userId: ${resolvedUserId}`,
74
- ` ${err.message}`,
75
- ``,
76
- `토큰을 다시 발급받거나 userId를 직접 지정해보세요.`,
77
- ].join('\n'),
78
- }],
79
- };
80
- }
18
+ // 구현은 instagram/setup.ts 있다 mimi-seed-social-auth CLI 와 공유한다.
19
+ const result = await connectInstagram(accessToken, userId, assumeIssuedNow);
20
+ return { content: [{ type: 'text', text: result.text }] };
81
21
  });
82
22
  server.tool('instagram_get_account', 'Instagram 계정 정보 조회 + 저장된 토큰 유효성 검증.', {}, async () => {
83
23
  const cfg = requireInstagramConfig();
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ // mimi-seed-social-auth — Facebook 페이지 / Instagram 계정 연결.
3
+ //
4
+ // 검증·저장 구현은 facebook/setup.ts · instagram/setup.ts 에 있고, MCP 도구
5
+ // (facebook_save_config / instagram_save_config)와 **같은 코드**를 호출한다.
6
+ // 토큰이 유효하지 않으면 저장하지 않는다.
7
+ //
8
+ // 사용:
9
+ // mimi-seed-social-auth → 무엇을 연결할지 물어봄
10
+ // mimi-seed-social-auth facebook → Facebook 만
11
+ // mimi-seed-social-auth instagram → Instagram 만
12
+ //
13
+ // 주의: connectFacebook / connectInstagram 이 돌려주는 result.text 는 MCP 도구와 공유하는
14
+ // 텍스트라 여기서 번역하지 않는다 (그대로 출력한다).
15
+ import readline from 'node:readline';
16
+ import { loadFacebookConfig } from '../facebook/config.js';
17
+ import { loadInstagramConfig } from '../instagram/config.js';
18
+ import { connectFacebook } from '../facebook/setup.js';
19
+ import { connectInstagram } from '../instagram/setup.js';
20
+ import { resolveLang } from '../lib/lang.js';
21
+ // ko 가 원본이고 en 은 `typeof ko` 를 만족해야 한다 — 키를 빠뜨리면 컴파일이 깨진다.
22
+ const ko = {
23
+ title: ' 🤖 Mimi Seed — 소셜 계정 연결 (Facebook / Instagram)',
24
+ already: (label, detail) => ` ✅ ${label} 이미 연결됨 (${detail})`,
25
+ reconnect: ' 다시 설정할래? (y/N): ',
26
+ skipped: ' 건너뜀.',
27
+ cancelled: ' 취소됨.',
28
+ verifying: ' 🔎 토큰 검증 중...',
29
+ notSaved: '\n (검증 실패 — 저장하지 않았어.)',
30
+ which: '\n 무엇을 연결할까? [f] Facebook [i] Instagram [b] 둘 다: ',
31
+ fbHeader: ' ── Facebook 페이지 ──',
32
+ fbHowTo: ' Page Access Token 발급:',
33
+ fbStep1: ' 1. Meta 앱 → Graph API Explorer',
34
+ fbStep2: ' 2. 권한: pages_show_list, pages_manage_posts, pages_read_engagement',
35
+ fbStep3: ' 3. User Token 생성 → /me/accounts 호출 → 그 페이지의 access_token (EAA…)',
36
+ fbNote: ' (long-lived 토큰 권장. pageId 는 토큰에서 자동 조회돼.)',
37
+ fbAskToken: ' Page Access Token (EAA…): ',
38
+ fbAskPageId: ' Page ID (선택, 엔터 시 자동 조회): ',
39
+ fbPickPage: '\n 어느 페이지를 쓸까? Page ID: ',
40
+ igHeader: ' ── Instagram ──',
41
+ igHowTo: ' Long-lived 토큰 두 형식 모두 지원 (자동 감지):',
42
+ igIgaa: ' • IGAA… — Instagram Login (Meta 신규, Facebook 페이지 불필요)',
43
+ igEaa: ' • EAA… — Facebook Login (IG **비즈니스** 계정 + FB 페이지 연결 필요)',
44
+ igNote: ' (userId 는 토큰에서 자동 조회돼. 토큰 수명은 약 60일.)',
45
+ igAskToken: ' Access Token (IGAA… 또는 EAA…): ',
46
+ igAskUserId: ' Instagram Business Account ID (선택, 엔터 시 자동 조회): ',
47
+ };
48
+ const en = {
49
+ title: ' 🤖 Mimi Seed — Connect social accounts (Facebook / Instagram)',
50
+ already: (label, detail) => ` ✅ ${label} already connected (${detail})`,
51
+ reconnect: ' Set it up again? (y/N): ',
52
+ skipped: ' Skipped.',
53
+ cancelled: ' Cancelled.',
54
+ verifying: ' 🔎 Verifying the token...',
55
+ notSaved: '\n (Verification failed — nothing was saved.)',
56
+ which: '\n What do you want to connect? [f] Facebook [i] Instagram [b] both: ',
57
+ fbHeader: ' ── Facebook Page ──',
58
+ fbHowTo: ' Get a Page Access Token:',
59
+ fbStep1: ' 1. Meta app → Graph API Explorer',
60
+ fbStep2: ' 2. Permissions: pages_show_list, pages_manage_posts, pages_read_engagement',
61
+ fbStep3: " 3. Generate a User Token → call /me/accounts → take that page's access_token (EAA…)",
62
+ fbNote: ' (A long-lived token is recommended. pageId is looked up from the token.)',
63
+ fbAskToken: ' Page Access Token (EAA…): ',
64
+ fbAskPageId: ' Page ID (optional, Enter to look it up): ',
65
+ fbPickPage: '\n Which page should I use? Page ID: ',
66
+ igHeader: ' ── Instagram ──',
67
+ igHowTo: ' Both long-lived token shapes are supported (auto-detected):',
68
+ igIgaa: ' • IGAA… — Instagram Login (new Meta flow, no Facebook Page needed)',
69
+ igEaa: ' • EAA… — Facebook Login (needs an IG **business** account linked to an FB Page)',
70
+ igNote: ' (userId is looked up from the token. Tokens last about 60 days.)',
71
+ igAskToken: ' Access Token (IGAA… or EAA…): ',
72
+ igAskUserId: ' Instagram Business Account ID (optional, Enter to look it up): ',
73
+ };
74
+ const M = resolveLang() === 'en' ? en : ko;
75
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
76
+ const ask = (q) => new Promise((r) => rl.question(q, (a) => r(a.trim())));
77
+ async function confirmReconnect(label, detail) {
78
+ console.log(M.already(label, detail));
79
+ const answer = await ask(M.reconnect);
80
+ return answer.toLowerCase() === 'y';
81
+ }
82
+ async function setupFacebook() {
83
+ console.log('');
84
+ console.log(M.fbHeader);
85
+ const existing = loadFacebookConfig();
86
+ if (existing && !(await confirmReconnect('Facebook', existing.pageName ?? existing.pageId))) {
87
+ return true;
88
+ }
89
+ console.log(M.fbHowTo);
90
+ console.log(M.fbStep1);
91
+ console.log(M.fbStep2);
92
+ console.log(M.fbStep3);
93
+ console.log(M.fbNote);
94
+ console.log('');
95
+ const token = await ask(M.fbAskToken);
96
+ if (!token) {
97
+ console.log(M.skipped);
98
+ return true;
99
+ }
100
+ let pageId = await ask(M.fbAskPageId);
101
+ console.log(M.verifying);
102
+ let result = await connectFacebook(token, pageId || undefined);
103
+ // 토큰이 여러 페이지에 닿으면 connectFacebook 은 목록만 주고 저장하지 않는다.
104
+ // 다시 실행하라고 내보내지 말고, 여기서 바로 골라 받는다.
105
+ if (!result.ok && result.text.includes('여러 페이지에')) {
106
+ console.log('');
107
+ console.log(indent(result.text));
108
+ pageId = await ask(M.fbPickPage);
109
+ if (!pageId) {
110
+ console.log(M.skipped);
111
+ return true;
112
+ }
113
+ console.log(M.verifying);
114
+ result = await connectFacebook(token, pageId);
115
+ }
116
+ console.log('');
117
+ console.log(indent(result.text));
118
+ if (!result.ok)
119
+ console.log(M.notSaved);
120
+ return result.ok;
121
+ }
122
+ async function setupInstagram() {
123
+ console.log('');
124
+ console.log(M.igHeader);
125
+ const existing = loadInstagramConfig();
126
+ if (existing && !(await confirmReconnect('Instagram', existing.username ?? existing.userId))) {
127
+ return true;
128
+ }
129
+ console.log(M.igHowTo);
130
+ console.log(M.igIgaa);
131
+ console.log(M.igEaa);
132
+ console.log(M.igNote);
133
+ console.log('');
134
+ const token = await ask(M.igAskToken);
135
+ if (!token) {
136
+ console.log(M.skipped);
137
+ return true;
138
+ }
139
+ const userId = await ask(M.igAskUserId);
140
+ console.log(M.verifying);
141
+ const result = await connectInstagram(token, userId || undefined);
142
+ console.log('');
143
+ console.log(indent(result.text));
144
+ if (!result.ok)
145
+ console.log(M.notSaved);
146
+ return result.ok;
147
+ }
148
+ function indent(text) {
149
+ return text
150
+ .split('\n')
151
+ .map((l) => ` ${l}`)
152
+ .join('\n');
153
+ }
154
+ async function main() {
155
+ const target = (process.argv[2] ?? '').toLowerCase();
156
+ console.log('');
157
+ console.log(M.title);
158
+ let ok = true;
159
+ if (target === 'facebook' || target === 'fb') {
160
+ ok = await setupFacebook();
161
+ }
162
+ else if (target === 'instagram' || target === 'ig') {
163
+ ok = await setupInstagram();
164
+ }
165
+ else {
166
+ const which = await ask(M.which);
167
+ const c = which.toLowerCase();
168
+ if (c === 'f')
169
+ ok = await setupFacebook();
170
+ else if (c === 'i')
171
+ ok = await setupInstagram();
172
+ else if (c === 'b') {
173
+ const fb = await setupFacebook();
174
+ const ig = await setupInstagram();
175
+ ok = fb && ig;
176
+ }
177
+ else {
178
+ console.log(M.cancelled);
179
+ }
180
+ }
181
+ console.log('');
182
+ rl.close();
183
+ // 토큰이 거부됐는데 exit 0 이면 호출한 마법사/스크립트가 성공으로 오인한다.
184
+ if (!ok)
185
+ process.exit(1);
186
+ }
187
+ main().catch((e) => {
188
+ console.error(`\n ❌ ${e instanceof Error ? e.message : String(e)}\n`);
189
+ rl.close();
190
+ process.exit(1);
191
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.6.6",
3
+ "version": "0.8.0",
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": {
@@ -9,6 +9,9 @@
9
9
  "mimi-seed-appstore-auth": "dist/appstore/setup-cli.js",
10
10
  "mimi-seed-playstore-auth": "dist/auth/playstore-setup-cli.js",
11
11
  "mimi-seed-bigquery-auth": "dist/auth/bigquery-setup-cli.js",
12
+ "mimi-seed-jenkins-auth": "dist/jenkins/setup-cli.js",
13
+ "mimi-seed-googleads-auth": "dist/googleads/setup-cli.js",
14
+ "mimi-seed-social-auth": "dist/social/setup-cli.js",
12
15
  "mimi-seed-firebase": "dist/firebase/cli.js",
13
16
  "mimi-seed-admob": "dist/admob/cli.js",
14
17
  "mimi-seed-ga4": "dist/ga4/cli.js"