@yoonion/mimi-seed-mcp 0.3.14 → 0.3.15

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.
@@ -395,20 +395,16 @@ async function getVersionAppAndPlatform(versionId) {
395
395
  return { appId, platform };
396
396
  }
397
397
  async function findOpenReviewSubmission(appId, platform) {
398
+ // ASC API는 filter[state]=CREATED를 더 이상 허용하지 않음 (READY_FOR_REVIEW, WAITING_FOR_REVIEW 등만 허용).
399
+ // CREATED 상태 submission은 별도로 조회 불가 → 이미 진행 중인 submission만 재사용.
400
+ // 없으면 submitVersionForReview가 새로 생성.
398
401
  const data = await apiGet('/reviewSubmissions', {
399
402
  'filter[app]': appId,
400
403
  'filter[platform]': platform,
401
- 'filter[state]': 'READY_FOR_REVIEW,COMPLETING,UNRESOLVED_ISSUES',
404
+ 'filter[state]': 'READY_FOR_REVIEW,WAITING_FOR_REVIEW,COMPLETING,UNRESOLVED_ISSUES',
402
405
  'limit': '1',
403
406
  });
404
- // CREATED 상태(아직 제출 안 된)도 별도 조회해서 우선 재사용
405
- const created = await apiGet('/reviewSubmissions', {
406
- 'filter[app]': appId,
407
- 'filter[platform]': platform,
408
- 'filter[state]': 'CREATED',
409
- 'limit': '1',
410
- });
411
- return created?.data?.[0]?.id ?? data?.data?.[0]?.id ?? null;
407
+ return data?.data?.[0]?.id ?? null;
412
408
  }
413
409
  async function isVersionAttached(submissionId, versionId) {
414
410
  const data = await apiGet(`/reviewSubmissions/${submissionId}/items`, {
@@ -0,0 +1,29 @@
1
+ import type { OAuth2Client } from 'google-auth-library';
2
+ export declare function runQuery(auth: OAuth2Client, projectId: string, query: string, maxResults?: number): Promise<{
3
+ jobComplete: boolean | null | undefined;
4
+ totalRows: string | null | undefined;
5
+ schema: {
6
+ name: string | null | undefined;
7
+ type: string | null | undefined;
8
+ }[];
9
+ rows: {
10
+ [k: string]: any;
11
+ }[];
12
+ }>;
13
+ export declare function listDatasets(auth: OAuth2Client, projectId: string): Promise<{
14
+ datasetId: string | null | undefined;
15
+ location: string | undefined;
16
+ }[]>;
17
+ export declare function listTables(auth: OAuth2Client, projectId: string, datasetId: string): Promise<{
18
+ tableId: string | null | undefined;
19
+ type: string | undefined;
20
+ }[]>;
21
+ export declare function getTableSchema(auth: OAuth2Client, projectId: string, datasetId: string, tableId: string): Promise<{
22
+ tableId: string;
23
+ schema: {
24
+ name: string | null | undefined;
25
+ type: string | null | undefined;
26
+ mode: string | null | undefined;
27
+ description: string | null | undefined;
28
+ }[] | undefined;
29
+ }>;
@@ -0,0 +1,53 @@
1
+ import { google } from 'googleapis';
2
+ const bq = () => google.bigquery('v2');
3
+ // ─── 쿼리 실행 ───
4
+ export async function runQuery(auth, projectId, query, maxResults = 1000) {
5
+ const res = await bq().jobs.query({
6
+ auth,
7
+ projectId,
8
+ requestBody: {
9
+ query,
10
+ useLegacySql: false,
11
+ maxResults,
12
+ timeoutMs: 30000,
13
+ },
14
+ });
15
+ const data = res.data;
16
+ const schema = data.schema?.fields ?? [];
17
+ const rows = (data.rows ?? []).map((row) => Object.fromEntries((row.f ?? []).map((cell, i) => [schema[i]?.name ?? `col${i}`, cell.v])));
18
+ return {
19
+ jobComplete: data.jobComplete,
20
+ totalRows: data.totalRows,
21
+ schema: schema.map((f) => ({ name: f.name, type: f.type })),
22
+ rows,
23
+ };
24
+ }
25
+ // ─── 데이터셋 목록 ───
26
+ export async function listDatasets(auth, projectId) {
27
+ const res = await bq().datasets.list({ auth, projectId });
28
+ return (res.data.datasets ?? []).map((d) => ({
29
+ datasetId: d.datasetReference?.datasetId,
30
+ location: d.location,
31
+ }));
32
+ }
33
+ // ─── 테이블 목록 ───
34
+ export async function listTables(auth, projectId, datasetId) {
35
+ const res = await bq().tables.list({ auth, projectId, datasetId });
36
+ return (res.data.tables ?? []).map((t) => ({
37
+ tableId: t.tableReference?.tableId,
38
+ type: t.type,
39
+ }));
40
+ }
41
+ // ─── 테이블 스키마 ───
42
+ export async function getTableSchema(auth, projectId, datasetId, tableId) {
43
+ const res = await bq().tables.get({ auth, projectId, datasetId, tableId });
44
+ return {
45
+ tableId,
46
+ schema: res.data.schema?.fields?.map((f) => ({
47
+ name: f.name,
48
+ type: f.type,
49
+ mode: f.mode,
50
+ description: f.description,
51
+ })),
52
+ };
53
+ }
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import * as playstore from './playstore/tools.js';
13
13
  import * as appstore from './appstore/tools.js';
14
14
  import * as appstoreScreenshots from './appstore/screenshots.js';
15
15
  import * as iam from './iam/tools.js';
16
+ import * as bigquery from './bigquery/tools.js';
16
17
  import { checkPlayStoreRisks, checkAppStoreRisks, formatRisks } from './checks/risks.js';
17
18
  import { buildPlayStoreReleasePlan, buildAppStoreReleasePlan } from './checks/plan.js';
18
19
  import { validateAppStoreScreenshots, validatePlayStoreScreenshots, formatValidationResults } from './checks/screenshots.js';
@@ -1407,9 +1408,73 @@ server.tool('generate_review_reply', [
1407
1408
  return { content: [{ type: 'text', text }] };
1408
1409
  });
1409
1410
  // ══════════════════════════════════════════════════
1411
+ // BigQuery
1412
+ // ══════════════════════════════════════════════════
1413
+ server.tool('bigquery_run_query', 'BigQuery SQL 쿼리 실행 (SELECT). GA4 analytics_* 테이블 분석에 사용.', {
1414
+ projectId: z.string().describe('GCP 프로젝트 ID (예: ads-coffee)'),
1415
+ query: z.string().describe('실행할 StandardSQL 쿼리'),
1416
+ maxResults: z.number().optional().describe('최대 행 수 (기본 1000)'),
1417
+ }, async ({ projectId, query, maxResults }) => {
1418
+ const auth = requireAuth();
1419
+ const result = await bigquery.runQuery(auth, projectId, query, maxResults ?? 1000);
1420
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1421
+ });
1422
+ server.tool('bigquery_list_datasets', 'BigQuery 프로젝트의 데이터셋 목록 조회', {
1423
+ projectId: z.string().describe('GCP 프로젝트 ID'),
1424
+ }, async ({ projectId }) => {
1425
+ const auth = requireAuth();
1426
+ const datasets = await bigquery.listDatasets(auth, projectId);
1427
+ return { content: [{ type: 'text', text: JSON.stringify(datasets, null, 2) }] };
1428
+ });
1429
+ server.tool('bigquery_list_tables', 'BigQuery 데이터셋의 테이블 목록 조회', {
1430
+ projectId: z.string().describe('GCP 프로젝트 ID'),
1431
+ datasetId: z.string().describe('데이터셋 ID (예: analytics_530080532)'),
1432
+ }, async ({ projectId, datasetId }) => {
1433
+ const auth = requireAuth();
1434
+ const tables = await bigquery.listTables(auth, projectId, datasetId);
1435
+ return { content: [{ type: 'text', text: JSON.stringify(tables, null, 2) }] };
1436
+ });
1437
+ server.tool('bigquery_get_table_schema', 'BigQuery 테이블 스키마(컬럼 정보) 조회', {
1438
+ projectId: z.string().describe('GCP 프로젝트 ID'),
1439
+ datasetId: z.string().describe('데이터셋 ID'),
1440
+ tableId: z.string().describe('테이블 ID'),
1441
+ }, async ({ projectId, datasetId, tableId }) => {
1442
+ const auth = requireAuth();
1443
+ const schema = await bigquery.getTableSchema(auth, projectId, datasetId, tableId);
1444
+ return { content: [{ type: 'text', text: JSON.stringify(schema, null, 2) }] };
1445
+ });
1446
+ // ══════════════════════════════════════════════════
1410
1447
  // Start
1411
1448
  // ══════════════════════════════════════════════════
1449
+ // `npx -y @yoonion/mimi-seed-mcp <subcommand>` 처리.
1450
+ // npx는 스코프 패키지의 basename(`mimi-seed-mcp`)을 매치해 이 bin을 실행하므로,
1451
+ // 추가 인자(`mimi-seed-auth` 등)는 여기 argv로 흘러들어온다. 이전엔 MCP 서버가
1452
+ // stdin을 기다리며 영구 hang 됐다 — 이제 sub-CLI로 위임한다.
1453
+ const SUBCOMMANDS = {
1454
+ 'mimi-seed-auth': () => import('./auth/cli.js'),
1455
+ 'mimi-seed-playstore-auth': () => import('./auth/playstore-setup-cli.js'),
1456
+ 'mimi-seed-appstore-auth': () => import('./appstore/setup-cli.js'),
1457
+ };
1412
1458
  async function main() {
1459
+ const sub = process.argv[2];
1460
+ if (sub && SUBCOMMANDS[sub]) {
1461
+ // argv에서 subcommand 토큰 제거 후 sub-CLI 모듈 import — 모듈 top-level이 main() 실행
1462
+ process.argv.splice(2, 1);
1463
+ await SUBCOMMANDS[sub]();
1464
+ return;
1465
+ }
1466
+ if (sub && !sub.startsWith('-')) {
1467
+ console.error([
1468
+ `❌ Unknown subcommand: ${sub}`,
1469
+ '',
1470
+ 'Available:',
1471
+ ...Object.keys(SUBCOMMANDS).map((k) => ` npx -y @yoonion/mimi-seed-mcp ${k}`),
1472
+ '',
1473
+ 'Or run the MCP server (no args):',
1474
+ ' npx -y @yoonion/mimi-seed-mcp',
1475
+ ].join('\n'));
1476
+ process.exit(2);
1477
+ }
1413
1478
  const transport = new StdioServerTransport();
1414
1479
  await server.connect(transport);
1415
1480
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.3.14",
3
+ "version": "0.3.15",
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": {