@yoonion/mimi-seed-mcp 0.19.0 → 0.19.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.
@@ -0,0 +1,239 @@
1
+ import { google } from '../lib/googleapis-lite.js';
2
+ import { getBillingInfo } from '../billing/tools.js';
3
+ const FREE_DAILY_FETCHES = 100_000;
4
+ const FIRST_PAID_TIER_END = 10_000_000;
5
+ const REMOTE_CONFIG_BASE = 'https://firebaseremoteconfig.googleapis.com/v1';
6
+ export function countStates(items) {
7
+ const counts = {};
8
+ for (const item of items) {
9
+ const state = item.state ?? 'UNSPECIFIED';
10
+ counts[state] = (counts[state] ?? 0) + 1;
11
+ }
12
+ return counts;
13
+ }
14
+ async function optionalRequest(auth, url) {
15
+ try {
16
+ const response = await auth.request({ url });
17
+ return { available: true, data: response.data };
18
+ }
19
+ catch (error) {
20
+ return {
21
+ available: false,
22
+ error: error instanceof Error ? error.message : String(error),
23
+ };
24
+ }
25
+ }
26
+ async function optionalPagedRequest(auth, url, field) {
27
+ try {
28
+ const items = [];
29
+ let pageToken;
30
+ let pages = 0;
31
+ do {
32
+ const requestUrl = new URL(url);
33
+ requestUrl.searchParams.set('pageSize', '100');
34
+ if (pageToken)
35
+ requestUrl.searchParams.set('pageToken', pageToken);
36
+ const response = await auth.request({ url: requestUrl.toString() });
37
+ const data = response.data;
38
+ items.push(...(data[field] ?? []));
39
+ pageToken = data.nextPageToken;
40
+ pages += 1;
41
+ } while (pageToken && pages < 10);
42
+ return { available: true, data: { items, pages, truncated: Boolean(pageToken) } };
43
+ }
44
+ catch (error) {
45
+ return {
46
+ available: false,
47
+ error: error instanceof Error ? error.message : String(error),
48
+ };
49
+ }
50
+ }
51
+ function numericPoint(point) {
52
+ const raw = point.value?.int64Value ?? point.value?.doubleValue ?? 0;
53
+ const value = typeof raw === 'string' ? Number.parseInt(raw, 10) : raw;
54
+ return Number.isFinite(value) ? value : 0;
55
+ }
56
+ export function estimateRemoteConfigDailyCost(fetches) {
57
+ if (fetches <= FREE_DAILY_FETCHES)
58
+ return 0;
59
+ const firstPaid = Math.min(fetches, FIRST_PAID_TIER_END) - FREE_DAILY_FETCHES;
60
+ const secondPaid = Math.max(0, fetches - FIRST_PAID_TIER_END);
61
+ return firstPaid * 0.000006 + secondPaid * 0.000001;
62
+ }
63
+ export function remoteConfigUsageLevel(fetches) {
64
+ const ratio = fetches / FREE_DAILY_FETCHES;
65
+ if (ratio >= 1)
66
+ return 'critical';
67
+ if (ratio >= 0.8)
68
+ return 'warning';
69
+ return 'ok';
70
+ }
71
+ async function fetchDailyUsage(auth, projectId, days) {
72
+ try {
73
+ const end = new Date();
74
+ const start = new Date(end.getTime() - days * 86_400_000);
75
+ const monitoring = google.monitoring('v3');
76
+ const response = await monitoring.projects.timeSeries.list({
77
+ auth,
78
+ name: `projects/${projectId}`,
79
+ filter: 'metric.type="firebaseremoteconfig.googleapis.com/project/fetch_request_count"',
80
+ 'interval.startTime': start.toISOString(),
81
+ 'interval.endTime': end.toISOString(),
82
+ 'aggregation.alignmentPeriod': '86400s',
83
+ 'aggregation.perSeriesAligner': 'ALIGN_SUM',
84
+ 'aggregation.crossSeriesReducer': 'REDUCE_SUM',
85
+ view: 'FULL',
86
+ pageSize: Math.max(days + 2, 10),
87
+ });
88
+ const points = (response.data.timeSeries ?? [])
89
+ .flatMap((series) => series.points ?? [])
90
+ .map((point) => ({
91
+ date: (point.interval?.endTime ?? end.toISOString()).slice(0, 10),
92
+ fetches: numericPoint(point),
93
+ }));
94
+ const byDate = new Map();
95
+ for (const point of points)
96
+ byDate.set(point.date, (byDate.get(point.date) ?? 0) + point.fetches);
97
+ const rows = [...byDate.entries()]
98
+ .map(([date, fetches]) => ({ date, fetches }))
99
+ .sort((a, b) => a.date.localeCompare(b.date));
100
+ return { available: true, data: rows };
101
+ }
102
+ catch (error) {
103
+ return {
104
+ available: false,
105
+ error: error instanceof Error ? error.message : String(error),
106
+ };
107
+ }
108
+ }
109
+ export async function getRemoteConfigOverview(auth, input) {
110
+ const namespace = input.namespace ?? 'firebase';
111
+ const days = Math.min(Math.max(input.days ?? 7, 1), 30);
112
+ const encodedProject = encodeURIComponent(input.projectId);
113
+ const encodedNamespace = encodeURIComponent(namespace);
114
+ const parent = `${REMOTE_CONFIG_BASE}/projects/${encodedProject}/namespaces/${encodedNamespace}`;
115
+ const [template, experiments, rollouts, usage, billing] = await Promise.all([
116
+ optionalRequest(auth, `${parent}/remoteConfig`),
117
+ optionalPagedRequest(auth, `${parent}/experiments`, 'experiments'),
118
+ optionalPagedRequest(auth, `${parent}/rollouts`, 'rollouts'),
119
+ fetchDailyUsage(auth, input.projectId, days),
120
+ getBillingInfo(auth, input.projectId)
121
+ .then((data) => ({ available: true, data }))
122
+ .catch((error) => ({
123
+ available: false,
124
+ error: error instanceof Error ? error.message : String(error),
125
+ })),
126
+ ]);
127
+ const daily = usage.data ?? [];
128
+ const latest = daily.at(-1);
129
+ const peak = daily.reduce((current, point) => !current || point.fetches > current.fetches ? point : current, undefined);
130
+ const withUsageProjection = (point) => ({
131
+ ...point,
132
+ utilizationPercent: Number(((point.fetches / FREE_DAILY_FETCHES) * 100).toFixed(2)),
133
+ level: remoteConfigUsageLevel(point.fetches),
134
+ projectedStandardDailyCostUsd: Number(estimateRemoteConfigDailyCost(point.fetches).toFixed(4)),
135
+ });
136
+ const summarizeExperiment = (item) => ({
137
+ name: item.name,
138
+ displayName: item.definition?.displayName,
139
+ state: item.state,
140
+ startTime: item.startTime,
141
+ endTime: item.endTime,
142
+ lastUpdateTime: item.lastUpdateTime,
143
+ });
144
+ const summarizeRollout = (item) => ({
145
+ name: item.name,
146
+ displayName: item.definition?.displayName,
147
+ state: item.state,
148
+ startTime: item.startTime,
149
+ endTime: item.endTime,
150
+ lastUpdateTime: item.lastUpdateTime,
151
+ });
152
+ const activeExperiments = experiments.data?.items
153
+ .filter((item) => item.state === 'RUNNING')
154
+ .map(summarizeExperiment) ?? [];
155
+ const activeRollouts = rollouts.data?.items
156
+ .filter((item) => item.state === 'RUNNING')
157
+ .map(summarizeRollout) ?? [];
158
+ const recentExperiments = [...(experiments.data?.items ?? [])]
159
+ .sort((left, right) => (right.lastUpdateTime ?? right.endTime ?? right.startTime ?? '')
160
+ .localeCompare(left.lastUpdateTime ?? left.endTime ?? left.startTime ?? ''))
161
+ .slice(0, 20)
162
+ .map(summarizeExperiment);
163
+ const recentRollouts = [...(rollouts.data?.items ?? [])]
164
+ .sort((left, right) => (right.lastUpdateTime ?? right.endTime ?? right.startTime ?? '')
165
+ .localeCompare(left.lastUpdateTime ?? left.endTime ?? left.startTime ?? ''))
166
+ .slice(0, 20)
167
+ .map(summarizeRollout);
168
+ return {
169
+ projectId: input.projectId,
170
+ namespace,
171
+ pricingPolicy: {
172
+ effectiveDate: '2026-09-01',
173
+ noCostDailyFetches: FREE_DAILY_FETCHES,
174
+ firstPaidTier: { from: 100_001, to: FIRST_PAID_TIER_END, usdPerRequest: 0.000006 },
175
+ secondPaidTier: { from: FIRST_PAID_TIER_END + 1, usdPerRequest: 0.000001 },
176
+ transitionNotice: 'Existing projects can have a plan-specific transition grace period; this tool reports projected standard pricing and does not infer whether a grace period applies.',
177
+ sourceUrl: 'https://firebase.google.com/docs/remote-config/pricing',
178
+ },
179
+ billing: billing.available
180
+ ? {
181
+ available: true,
182
+ plan: billing.data.billingEnabled ? 'blaze' : 'spark',
183
+ billingEnabled: billing.data.billingEnabled,
184
+ }
185
+ : billing,
186
+ usage: usage.available
187
+ ? {
188
+ available: true,
189
+ metric: 'firebaseremoteconfig.googleapis.com/project/fetch_request_count',
190
+ days: daily,
191
+ latest: latest ? withUsageProjection(latest) : null,
192
+ peak: peak ? withUsageProjection(peak) : null,
193
+ }
194
+ : usage,
195
+ template: template.available
196
+ ? {
197
+ available: true,
198
+ version: template.data?.version ?? null,
199
+ parameterCount: Object.keys(template.data?.parameters ?? {}).length,
200
+ parameterGroupCount: Object.keys(template.data?.parameterGroups ?? {}).length,
201
+ conditionCount: template.data?.conditions?.length ?? 0,
202
+ }
203
+ : template,
204
+ experiments: experiments.available
205
+ ? {
206
+ available: true,
207
+ total: experiments.data?.items.length ?? 0,
208
+ stateCounts: countStates(experiments.data?.items ?? []),
209
+ active: activeExperiments,
210
+ recent: recentExperiments,
211
+ pages: experiments.data?.pages ?? 0,
212
+ truncated: experiments.data?.truncated ?? false,
213
+ }
214
+ : experiments,
215
+ rollouts: rollouts.available
216
+ ? {
217
+ available: true,
218
+ total: rollouts.data?.items.length ?? 0,
219
+ stateCounts: countStates(rollouts.data?.items ?? []),
220
+ active: activeRollouts,
221
+ recent: recentRollouts,
222
+ pages: rollouts.data?.pages ?? 0,
223
+ truncated: rollouts.data?.truncated ?? false,
224
+ }
225
+ : rollouts,
226
+ warnings: [
227
+ ...(peak && remoteConfigUsageLevel(peak.fetches) !== 'ok'
228
+ ? [`Peak daily fetch usage is ${remoteConfigUsageLevel(peak.fetches)} at ${peak.fetches.toLocaleString()} requests on ${peak.date}.`]
229
+ : []),
230
+ ...(peak && peak.fetches >= FREE_DAILY_FETCHES && billing.available && !billing.data.billingEnabled
231
+ ? ['Spark projects risk throttling above 100,000 daily fetches after the applicable grace period; upgrade to Blaze for uninterrupted overage.']
232
+ : []),
233
+ ...(!usage.available ? ['Cloud Monitoring usage is unavailable; no fetch count was estimated.'] : []),
234
+ ...(!billing.available ? ['Billing plan could not be verified; cost/throttling interpretation is incomplete.'] : []),
235
+ ...(experiments.data?.truncated ? ['Experiment list exceeded 1,000 items and was truncated.'] : []),
236
+ ...(rollouts.data?.truncated ? ['Rollout list exceeded 1,000 items and was truncated.'] : []),
237
+ ],
238
+ };
239
+ }
@@ -15,6 +15,7 @@ export declare const google: {
15
15
  readonly cloudresourcemanager: typeof import("googleapis/build/src/apis/cloudresourcemanager/index.js").cloudresourcemanager;
16
16
  readonly firebase: typeof import("googleapis/build/src/apis/firebase/index.js").firebase;
17
17
  readonly iam: typeof import("googleapis/build/src/apis/iam/index.js").iam;
18
+ readonly monitoring: typeof import("googleapis/build/src/apis/monitoring/index.js").monitoring;
18
19
  readonly searchconsole: typeof import("googleapis/build/src/apis/searchconsole/index.js").searchconsole;
19
20
  readonly serviceusage: typeof import("googleapis/build/src/apis/serviceusage/index.js").serviceusage;
20
21
  readonly youtube: typeof import("googleapis/build/src/apis/youtube/index.js").youtube;
@@ -79,6 +79,9 @@ export const google = {
79
79
  get iam() {
80
80
  return sub('iam').iam;
81
81
  },
82
+ get monitoring() {
83
+ return sub('monitoring').monitoring;
84
+ },
82
85
  get searchconsole() {
83
86
  return sub('searchconsole').searchconsole;
84
87
  },
@@ -8,6 +8,7 @@ import * as appstoreDeclarations from '../appstore/declarations.js';
8
8
  import * as testflight from '../appstore/testflight.js';
9
9
  import * as previews from '../appstore/previews.js';
10
10
  import * as appstoreSales from '../appstore/sales.js';
11
+ import { getWeeklyInsight } from '../appstore/analytics.js';
11
12
  import { createAppleOneTimePurchase, createAppleSubscription, updateAppleProduct, deleteAppleProduct, listAppleProducts, } from '@onesub/providers';
12
13
  import { requireAppStoreCreds } from '../helpers.js';
13
14
  import { buildAppStoreReleasePlan } from '../checks/plan.js';
@@ -20,6 +21,15 @@ function stringifyAttr(value) {
20
21
  return typeof value === 'string' ? value : JSON.stringify(value);
21
22
  }
22
23
  export function registerAppstoreTools(server) {
24
+ server.tool('appstore_get_weekly_insight', [
25
+ 'App Store Connect Analytics의 표준 주간 Engagement·Downloads·Purchases 리포트를 내려받아 지난 두 기간을 비교합니다.',
26
+ '제품 페이지·획득·수익화 중 가장 크게 악화된 한 영역과 근거 지표, 실행할 개선안 하나를 반환합니다.',
27
+ 'ONGOING report request가 없으면 기본은 생성하지 않습니다. createIfMissing=true와 confirmCreate=true를 함께 줘야 생성하며 최초 데이터는 24~48시간 뒤 제공됩니다.',
28
+ ].join(' '), {
29
+ appId: z.string().describe('App Store 앱 ID'),
30
+ createIfMissing: z.boolean().optional().describe('ONGOING Analytics report request가 없을 때 생성 준비'),
31
+ confirmCreate: z.boolean().optional().describe('지속 Analytics report request 생성 명시 확인'),
32
+ }, async (args) => jsonResult(await getWeeklyInsight(args)));
23
33
  server.tool('appstore_list_apps', 'App Store Connect 앱 목록 조회', {}, async () => {
24
34
  const apps = await appstore.listApps();
25
35
  return jsonResult(apps);
@@ -1070,6 +1080,8 @@ export function registerAppstoreTools(server) {
1070
1080
  lootBox: z.boolean().optional().describe('확률형 아이템(루트박스) 포함 여부'),
1071
1081
  unrestrictedWebAccess: z.boolean().optional().describe('제한 없는 웹 접근'),
1072
1082
  userGeneratedContent: z.boolean().optional().describe('사용자 생성 콘텐츠'),
1083
+ socialMedia: z.boolean().optional().describe('UGC를 피드·탐색 방식으로 재배포·확산·상호작용하는 소셜 미디어 기능'),
1084
+ socialMediaAgeRestricted: z.boolean().optional().describe('소셜 미디어 기능을 13세 미만 사용자에게 비활성화'),
1073
1085
  messagingAndChat: z.boolean().optional().describe('메시지/채팅 기능'),
1074
1086
  advertising: z.boolean().optional().describe('광고 포함'),
1075
1087
  healthOrWellnessTopics: z.boolean().optional().describe('건강/웰니스 주제'),
@@ -5,7 +5,15 @@ import { requireAuth, requirePlayStoreAuth } from '../helpers.js';
5
5
  import * as appstore from '../appstore/tools.js';
6
6
  import * as playstore from '../playstore/tools.js';
7
7
  import { jsonResult } from '../lib/mcp-response.js';
8
+ import { checkBillingCompliance } from '../checks/billing.js';
8
9
  export function registerChecksTools(server) {
10
+ server.tool('android_check_billing_compliance', [
11
+ '로컬 Android 저장소의 Google Play Billing Library 버전을 읽기 전용으로 탐지하고 제출 정책 마감과 비교합니다.',
12
+ 'build.gradle, build.gradle.kts, gradle/libs.versions.toml의 literal·변수·version catalog를 검사합니다.',
13
+ '코드를 자동 수정하지 않고 공식 Android CLI Skill 설치 명령과 업그레이드 프롬프트만 반환합니다.',
14
+ ].join(' '), {
15
+ projectPath: z.string().optional().describe('검사할 프로젝트 절대경로 (기본: MCP 프로세스 현재 디렉터리)'),
16
+ }, async ({ projectPath }) => jsonResult(await checkBillingCompliance(projectPath ?? process.cwd())));
9
17
  server.tool('playstore_check_submission_risks', [
10
18
  'Google Play 제출 전 위험 요소를 자동으로 점검합니다.',
11
19
  '블로커(반드시 수정)와 경고(권장 수정)로 분류하여 반환합니다.',
@@ -23,11 +31,17 @@ export function registerChecksTools(server) {
23
31
  'App Store 제출 전 위험 요소를 자동으로 점검합니다.',
24
32
  '블로커(반드시 수정)와 경고(권장 수정)로 분류하여 반환합니다.',
25
33
  '점검 항목: 메타데이터 완성도(설명/What\'s New/키워드), 스크린샷, TestFlight 빌드, 개인정보처리방침 URL.',
34
+ '2026년 9월 필수화된 소셜 미디어 연령등급 응답을 확인하고, 기대 기능 값을 주면 App Store Connect 응답과 비교합니다.',
26
35
  'App Store 인증이 필요합니다 (appstore_* 도구 사용 가능 상태여야 함).',
27
36
  ].join(' '), {
28
37
  appId: z.string().describe('App Store 앱 ID (appstore_list_apps 결과의 id 필드)'),
29
- }, async ({ appId }) => {
30
- const risks = await checkAppStoreRisks(appId);
38
+ socialMediaCapability: z.boolean().optional().describe('앱이 소셜 피드·UGC 확산 기능을 제공하는지에 대한 프로젝트 사실'),
39
+ socialMediaAgeRestricted: z.boolean().optional().describe('소셜 미디어 기능이 13세 미만에게 비활성화되는지에 대한 프로젝트 사실'),
40
+ }, async ({ appId, socialMediaCapability, socialMediaAgeRestricted }) => {
41
+ const risks = await checkAppStoreRisks(appId, {
42
+ socialMedia: socialMediaCapability,
43
+ socialMediaAgeRestricted,
44
+ });
31
45
  const text = formatRisks(risks, 'App Store');
32
46
  return { content: [{ type: 'text', text }] };
33
47
  });
@@ -4,6 +4,7 @@ import { requireAuth } from '../helpers.js';
4
4
  import { CLOUD_PLATFORM_SCOPE } from '../auth/scopes.js';
5
5
  import { friendlyGoogleError } from '../lib/google-errors.js';
6
6
  import { jsonResult, textResult } from '../lib/mcp-response.js';
7
+ import { getRemoteConfigOverview } from '../firebase/remote-config.js';
7
8
  // 모든 firebase tools 호출을 친절 에러로 감싸는 프록시 — 17개 핸들러에 개별
8
9
  // try/catch 없이 raw GaxiosError(API 미활성화/프로젝트 없음/billing/권한)를
9
10
  // "다음에 뭘 할지" 메시지로 변환. 비-Promise 반환은 그대로 통과.
@@ -29,6 +30,18 @@ const firebase = new Proxy(firebaseRaw, {
29
30
  },
30
31
  });
31
32
  export function registerFirebaseTools(server) {
33
+ server.tool('firebase_get_remote_config_overview', [
34
+ 'Firebase Remote Config의 일일 fetch 사용량, 과금 임계치, 현재 템플릿, A/B 실험과 rollout을 한 번에 조회합니다.',
35
+ '사용량은 Cloud Monitoring의 공식 fetch_request_count 메트릭을 사용하며, 조회 권한이 없으면 숫자를 추정하지 않고 unavailable로 반환합니다.',
36
+ '읽기 전용입니다. 10만 건 무료 구간 대비 80%부터 warning, 100%부터 critical로 표시합니다.',
37
+ ].join(' '), {
38
+ projectId: z.string().describe('Firebase / Google Cloud 프로젝트 ID'),
39
+ namespace: z.string().optional().describe('Remote Config namespace (기본: firebase)'),
40
+ days: z.number().int().min(1).max(30).optional().describe('사용량 조회 일수 (기본: 7, 최대: 30)'),
41
+ }, async ({ projectId, namespace, days }) => {
42
+ const auth = await requireAuth();
43
+ return jsonResult(await getRemoteConfigOverview(auth, { projectId, namespace, days }));
44
+ });
32
45
  server.tool('firebase_list_projects', '내 Firebase 프로젝트 목록 조회', {}, async () => {
33
46
  const auth = await requireAuth();
34
47
  const projects = await firebase.listProjects(auth);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.19.0",
3
+ "version": "0.19.2",
4
4
  "description": "Mimi Seed MCP server \u2014 Firebase + AdMob + Google Play + App Store management for Claude Code / Codex / Cursor / any MCP client.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "등록된 MCP 도구 + 도메인 메타데이터(label·credential·summary)의 SSOT. src/__tests__/tool-manifest.test.ts 가 실제 서버 등록 목록과 diff 하고 메타데이터 정합성을 검사한다. 도구 추가/삭제/개명 시 이 파일을 함께 갱신할 것 — 산문 문서에는 정확한 개수를 쓰지 말고 이 파일을 가리킬 것. mimi-seed://tools/catalog 리소스가 이 파일을 그대로 서빙한다.",
3
- "total": 231,
3
+ "total": 234,
4
4
  "domains": {
5
5
  "admob": {
6
6
  "label": "AdMob",
@@ -38,7 +38,7 @@
38
38
  "appstore": {
39
39
  "label": "App Store Connect",
40
40
  "credential": "ASC API 키 (mimi-seed auth appstore)",
41
- "summary": "버전·빌드 attach·What's New·스크린샷·IAP 심사 메타데이터·심사 제출·매출/재무 리포트",
41
+ "summary": "버전·빌드 attach·What's New·스크린샷·IAP 심사 메타데이터·심사 제출·매출/재무·주간 인사이트",
42
42
  "tools": [
43
43
  "appstore_list_apps",
44
44
  "appstore_verify_credentials",
@@ -102,6 +102,7 @@
102
102
  "appstore_upload_preview",
103
103
  "appstore_delete_preview",
104
104
  "appstore_get_sales_report",
105
+ "appstore_get_weekly_insight",
105
106
  "appstore_get_finance_report"
106
107
  ]
107
108
  },
@@ -142,10 +143,11 @@
142
143
  "checks": {
143
144
  "label": "출시 점검",
144
145
  "credential": "점검 대상 스토어의 자격증명",
145
- "summary": "제출 전 위험 점검·스크린샷 규격 검증·릴리스 상태",
146
+ "summary": "제출 전 위험·Billing 정책 점검·스크린샷 규격 검증·릴리스 상태",
146
147
  "tools": [
147
148
  "playstore_check_submission_risks",
148
149
  "appstore_check_submission_risks",
150
+ "android_check_billing_compliance",
149
151
  "screenshot_validate",
150
152
  "release_status"
151
153
  ]
@@ -179,7 +181,7 @@
179
181
  "firebase": {
180
182
  "label": "Firebase",
181
183
  "credential": "Google OAuth",
182
- "summary": "프로젝트/앱 생성·설정 파일 다운로드·서비스 활성화",
184
+ "summary": "프로젝트/앱 생성·설정 파일 다운로드·서비스 활성화·Remote Config 운영 현황",
183
185
  "tools": [
184
186
  "firebase_list_projects",
185
187
  "firebase_get_project",
@@ -200,7 +202,8 @@
200
202
  "firebase_enable_common_services",
201
203
  "firebase_list_enabled_services",
202
204
  "firebase_link_analytics",
203
- "firebase_get_analytics_details"
205
+ "firebase_get_analytics_details",
206
+ "firebase_get_remote_config_overview"
204
207
  ]
205
208
  },
206
209
  "ga4": {