@tmhs/mobile-mcp 0.10.0 → 0.11.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/dist/index.js CHANGED
@@ -31,9 +31,12 @@ import { register as registerSecurityAudit } from "./tools/securityAudit.js";
31
31
  import { register as registerProfilePerformance } from "./tools/profilePerformance.js";
32
32
  import { register as registerCheckOfflineReady } from "./tools/checkOfflineReady.js";
33
33
  import { register as registerSetupMonitoring } from "./tools/setupMonitoring.js";
34
+ import { register as registerSetupTheming } from "./tools/setupTheming.js";
35
+ import { register as registerAuditAccessibility } from "./tools/auditAccessibility.js";
36
+ import { register as registerSetupFeatureFlags } from "./tools/setupFeatureFlags.js";
34
37
  const server = new McpServer({
35
38
  name: "mobile-mcp",
36
- version: "0.10.0",
39
+ version: "0.11.0",
37
40
  });
38
41
  registerCheckDevEnvironment(server);
39
42
  registerScaffoldProject(server);
@@ -65,6 +68,9 @@ registerSecurityAudit(server);
65
68
  registerProfilePerformance(server);
66
69
  registerCheckOfflineReady(server);
67
70
  registerSetupMonitoring(server);
71
+ registerSetupTheming(server);
72
+ registerAuditAccessibility(server);
73
+ registerSetupFeatureFlags(server);
68
74
  async function main() {
69
75
  const transport = new StdioServerTransport();
70
76
  await server.connect(transport);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=auditAccessibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auditAccessibility.d.ts","sourceRoot":"","sources":["../../src/tools/auditAccessibility.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsNzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2ChD"}
@@ -0,0 +1,236 @@
1
+ import { z } from "zod";
2
+ import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
3
+ import { join, extname } from "node:path";
4
+ import { textResponse, errorResponse } from "../types.js";
5
+ const inputSchema = {
6
+ project_path: z
7
+ .string()
8
+ .optional()
9
+ .describe("Absolute path to the project root. Defaults to cwd."),
10
+ framework: z
11
+ .enum(["expo", "flutter"])
12
+ .optional()
13
+ .default("expo")
14
+ .describe("Framework to audit (default: expo)."),
15
+ };
16
+ function collectFiles(dir, extensions, maxDepth = 5, depth = 0) {
17
+ if (depth >= maxDepth || !existsSync(dir))
18
+ return [];
19
+ const results = [];
20
+ try {
21
+ for (const entry of readdirSync(dir)) {
22
+ if (entry.startsWith(".") || entry === "node_modules" || entry === "build" || entry === ".dart_tool")
23
+ continue;
24
+ const full = join(dir, entry);
25
+ try {
26
+ const stat = statSync(full);
27
+ if (stat.isDirectory()) {
28
+ results.push(...collectFiles(full, extensions, maxDepth, depth + 1));
29
+ }
30
+ else if (extensions.includes(extname(entry))) {
31
+ results.push(full);
32
+ }
33
+ }
34
+ catch { /* skip */ }
35
+ }
36
+ }
37
+ catch { /* skip */ }
38
+ return results;
39
+ }
40
+ function auditExpo(root) {
41
+ const violations = [];
42
+ const srcDir = existsSync(join(root, "src")) ? join(root, "src") : join(root, "app");
43
+ const files = collectFiles(srcDir, [".tsx", ".jsx"]);
44
+ for (const file of files) {
45
+ let content;
46
+ try {
47
+ content = readFileSync(file, "utf-8");
48
+ }
49
+ catch {
50
+ continue;
51
+ }
52
+ const rel = file.replace(root, "").replace(/\\/g, "/");
53
+ const touchableRegex = /<(TouchableOpacity|TouchableHighlight|Pressable|TouchableWithoutFeedback)\b/g;
54
+ let match;
55
+ while ((match = touchableRegex.exec(content)) !== null) {
56
+ const afterTag = content.slice(match.index, match.index + 300);
57
+ if (!afterTag.includes("accessibilityLabel") && !afterTag.includes("aria-label") && !afterTag.includes("accessible={false}")) {
58
+ violations.push({
59
+ severity: "critical",
60
+ wcag: "1.1.1 Non-text Content",
61
+ message: `<${match[1]}> without accessibilityLabel. Screen readers cannot describe this control.`,
62
+ fix: `Add accessibilityLabel="descriptive text" to the <${match[1]}>.`,
63
+ file: rel,
64
+ });
65
+ }
66
+ if (!afterTag.includes("accessibilityRole") && !afterTag.includes("role=")) {
67
+ violations.push({
68
+ severity: "serious",
69
+ wcag: "4.1.2 Name, Role, Value",
70
+ message: `<${match[1]}> without accessibilityRole. Screen readers do not know this is a button.`,
71
+ fix: `Add accessibilityRole="button" (or "link", "tab", etc.).`,
72
+ file: rel,
73
+ });
74
+ }
75
+ }
76
+ const imageRegex = /<Image\b/g;
77
+ while ((match = imageRegex.exec(content)) !== null) {
78
+ const afterTag = content.slice(match.index, match.index + 300);
79
+ if (!afterTag.includes("accessibilityLabel") && !afterTag.includes("aria-label") && !afterTag.includes("accessible={false}")) {
80
+ violations.push({
81
+ severity: "serious",
82
+ wcag: "1.1.1 Non-text Content",
83
+ message: "<Image> without accessibilityLabel. Decorative images should set accessible={false}.",
84
+ fix: "Add accessibilityLabel describing the image, or accessible={false} for decorative images.",
85
+ file: rel,
86
+ });
87
+ }
88
+ }
89
+ const smallSizeRegex = /(?:width|height|minWidth|minHeight)\s*:\s*(\d+)/g;
90
+ while ((match = smallSizeRegex.exec(content)) !== null) {
91
+ const size = parseInt(match[1], 10);
92
+ if (size > 0 && size < 44) {
93
+ const context = content.slice(Math.max(0, match.index - 100), match.index + 50);
94
+ if (/Touchable|Pressable|Button|onPress/i.test(context)) {
95
+ violations.push({
96
+ severity: "serious",
97
+ wcag: "2.5.5 Target Size",
98
+ message: `Touch target size ${size}px is below the 44px minimum. Difficult for users with motor impairments.`,
99
+ fix: "Set minimum width and height to 44 (or use hitSlop to expand the tappable area).",
100
+ file: rel,
101
+ });
102
+ }
103
+ }
104
+ }
105
+ if (content.includes("color:") && !content.includes("accessibilityLabel") && /color:\s*["']?(red|green|#ff0000|#00ff00)/i.test(content)) {
106
+ violations.push({
107
+ severity: "moderate",
108
+ wcag: "1.4.1 Use of Color",
109
+ message: "Color may be the only indicator of state. Color-blind users cannot distinguish this.",
110
+ fix: "Add a text label, icon, or pattern in addition to the color change.",
111
+ file: rel,
112
+ });
113
+ }
114
+ }
115
+ if (violations.length === 0) {
116
+ violations.push({
117
+ severity: "minor",
118
+ wcag: "General",
119
+ message: "No common a11y violations detected in source files. Test with VoiceOver/TalkBack for full coverage.",
120
+ fix: "Run the app with a screen reader and verify all flows are navigable.",
121
+ });
122
+ }
123
+ return violations;
124
+ }
125
+ function auditFlutter(root) {
126
+ const violations = [];
127
+ const libDir = join(root, "lib");
128
+ const files = collectFiles(libDir, [".dart"]);
129
+ for (const file of files) {
130
+ let content;
131
+ try {
132
+ content = readFileSync(file, "utf-8");
133
+ }
134
+ catch {
135
+ continue;
136
+ }
137
+ const rel = file.replace(root, "").replace(/\\/g, "/");
138
+ const gestureDetectorRegex = /GestureDetector\(/g;
139
+ let match;
140
+ while ((match = gestureDetectorRegex.exec(content)) !== null) {
141
+ const afterTag = content.slice(match.index, match.index + 500);
142
+ if (!afterTag.includes("Semantics") && !afterTag.includes("Tooltip")) {
143
+ violations.push({
144
+ severity: "critical",
145
+ wcag: "1.1.1 Non-text Content",
146
+ message: "GestureDetector without Semantics wrapper. Invisible to screen readers.",
147
+ fix: "Wrap in Semantics(label: '...', button: true, child: GestureDetector(...)).",
148
+ file: rel,
149
+ });
150
+ }
151
+ }
152
+ const inkwellRegex = /InkWell\(/g;
153
+ while ((match = inkwellRegex.exec(content)) !== null) {
154
+ const afterTag = content.slice(match.index, match.index + 500);
155
+ if (!afterTag.includes("Semantics") && !afterTag.includes("Tooltip") && !afterTag.includes("semanticsLabel")) {
156
+ violations.push({
157
+ severity: "serious",
158
+ wcag: "4.1.2 Name, Role, Value",
159
+ message: "InkWell without Semantics. Screen readers cannot describe this interactive element.",
160
+ fix: "Wrap in Semantics(label: '...', button: true) or use a named widget like ElevatedButton.",
161
+ file: rel,
162
+ });
163
+ }
164
+ }
165
+ const imageRegex = /Image\.(asset|network|file|memory)\(/g;
166
+ while ((match = imageRegex.exec(content)) !== null) {
167
+ const afterTag = content.slice(match.index, match.index + 300);
168
+ if (!afterTag.includes("semanticsLabel") && !afterTag.includes("Semantics") && !afterTag.includes("excludeFromSemantics")) {
169
+ violations.push({
170
+ severity: "serious",
171
+ wcag: "1.1.1 Non-text Content",
172
+ message: `Image.${match[1]}() without semanticsLabel. Screen readers skip this image.`,
173
+ fix: "Add semanticsLabel: '...' or excludeFromSemantics: true for decorative images.",
174
+ file: rel,
175
+ });
176
+ }
177
+ }
178
+ const sizedBoxRegex = /SizedBox\(\s*(?:width|height)\s*:\s*(\d+)/g;
179
+ while ((match = sizedBoxRegex.exec(content)) !== null) {
180
+ const size = parseInt(match[1], 10);
181
+ if (size > 0 && size < 44) {
182
+ const context = content.slice(Math.max(0, match.index - 200), match.index + 100);
183
+ if (/onTap|onPressed|InkWell|GestureDetector|IconButton/i.test(context)) {
184
+ violations.push({
185
+ severity: "serious",
186
+ wcag: "2.5.5 Target Size",
187
+ message: `Touch target constrained to ${size}px. Below the 44px minimum.`,
188
+ fix: "Ensure interactive elements have at least 48x48 logical pixels (Material guideline).",
189
+ file: rel,
190
+ });
191
+ }
192
+ }
193
+ }
194
+ }
195
+ if (violations.length === 0) {
196
+ violations.push({
197
+ severity: "minor",
198
+ wcag: "General",
199
+ message: "No common a11y violations detected. Test with TalkBack (Android) and VoiceOver (iOS).",
200
+ fix: "Enable TalkBack in device settings and navigate the entire app by swipe gestures.",
201
+ });
202
+ }
203
+ return violations;
204
+ }
205
+ export function register(server) {
206
+ server.tool("mobile_auditAccessibility", "Scan a mobile project for accessibility violations: missing labels, small touch targets, images without alt text, color-only indicators. Reports WCAG level for each finding.", inputSchema, async (args) => {
207
+ try {
208
+ const root = args.project_path || process.cwd();
209
+ if (!existsSync(root)) {
210
+ return errorResponse(new Error(`Project path does not exist: ${root}`));
211
+ }
212
+ const violations = args.framework === "flutter"
213
+ ? auditFlutter(root)
214
+ : auditExpo(root);
215
+ const criticalCount = violations.filter((v) => v.severity === "critical").length;
216
+ const seriousCount = violations.filter((v) => v.severity === "serious").length;
217
+ const moderateCount = violations.filter((v) => v.severity === "moderate").length;
218
+ return textResponse(JSON.stringify({
219
+ success: true,
220
+ framework: args.framework,
221
+ summary: {
222
+ total_violations: violations.length,
223
+ critical: criticalCount,
224
+ serious: seriousCount,
225
+ moderate: moderateCount,
226
+ minor: violations.length - criticalCount - seriousCount - moderateCount,
227
+ },
228
+ violations,
229
+ }, null, 2));
230
+ }
231
+ catch (err) {
232
+ return errorResponse(err);
233
+ }
234
+ });
235
+ }
236
+ //# sourceMappingURL=auditAccessibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auditAccessibility.js","sourceRoot":"","sources":["../../src/tools/auditAccessibility.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=setupFeatureFlags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupFeatureFlags.d.ts","sourceRoot":"","sources":["../../src/tools/setupFeatureFlags.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqNzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwFhD"}
@@ -0,0 +1,266 @@
1
+ import { z } from "zod";
2
+ import { writeFileSync, mkdirSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { textResponse, errorResponse } from "../types.js";
5
+ const inputSchema = {
6
+ project_path: z
7
+ .string()
8
+ .optional()
9
+ .describe("Absolute path to the project root. Defaults to cwd."),
10
+ framework: z
11
+ .enum(["expo", "flutter"])
12
+ .optional()
13
+ .default("expo")
14
+ .describe("Framework (default: expo)."),
15
+ provider: z
16
+ .enum(["posthog", "launchdarkly", "firebase"])
17
+ .optional()
18
+ .default("posthog")
19
+ .describe("Feature flag provider (default: posthog)."),
20
+ output_directory: z
21
+ .string()
22
+ .optional()
23
+ .default("lib")
24
+ .describe("Output directory relative to project root (default: lib)."),
25
+ };
26
+ function generatePostHogExpo() {
27
+ return `import PostHog from "posthog-react-native";
28
+
29
+ const posthog = new PostHog(process.env.EXPO_PUBLIC_POSTHOG_KEY!, {
30
+ host: process.env.EXPO_PUBLIC_POSTHOG_HOST ?? "https://us.i.posthog.com",
31
+ });
32
+
33
+ export type FeatureFlags = {
34
+ new_onboarding: boolean;
35
+ premium_features: boolean;
36
+ experimental_ui: boolean;
37
+ };
38
+
39
+ const FLAG_DEFAULTS: FeatureFlags = {
40
+ new_onboarding: false,
41
+ premium_features: false,
42
+ experimental_ui: false,
43
+ };
44
+
45
+ export function getFlag<K extends keyof FeatureFlags>(key: K): FeatureFlags[K] {
46
+ const value = posthog.getFeatureFlag(key);
47
+ if (value === undefined || value === null) return FLAG_DEFAULTS[key];
48
+ return value as FeatureFlags[K];
49
+ }
50
+
51
+ export function useFeatureFlag<K extends keyof FeatureFlags>(key: K): FeatureFlags[K] {
52
+ const value = posthog.useFeatureFlag(key);
53
+ if (value === undefined || value === null) return FLAG_DEFAULTS[key];
54
+ return value as FeatureFlags[K];
55
+ }
56
+
57
+ export function identifyUser(userId: string, properties?: Record<string, unknown>): void {
58
+ posthog.identify(userId, properties);
59
+ }
60
+
61
+ export function resetUser(): void {
62
+ posthog.reset();
63
+ }
64
+
65
+ export { posthog };
66
+ `;
67
+ }
68
+ function generateLaunchDarklyExpo() {
69
+ return `import { LDClient, LDConfig, LDContext } from "@launchdarkly/react-native-client-sdk";
70
+
71
+ const config: LDConfig = {
72
+ mobileKey: process.env.EXPO_PUBLIC_LAUNCHDARKLY_KEY!,
73
+ debug: __DEV__,
74
+ };
75
+
76
+ const client = new LDClient();
77
+
78
+ export type FeatureFlags = {
79
+ new_onboarding: boolean;
80
+ premium_features: boolean;
81
+ experimental_ui: boolean;
82
+ };
83
+
84
+ const FLAG_DEFAULTS: FeatureFlags = {
85
+ new_onboarding: false,
86
+ premium_features: false,
87
+ experimental_ui: false,
88
+ };
89
+
90
+ let initialized = false;
91
+
92
+ export async function initFeatureFlags(userId: string): Promise<void> {
93
+ if (initialized) return;
94
+ const context: LDContext = { kind: "user", key: userId };
95
+ await client.configure(config, context);
96
+ initialized = true;
97
+ }
98
+
99
+ export function getFlag<K extends keyof FeatureFlags>(key: K): FeatureFlags[K] {
100
+ return (client.boolVariation(key, FLAG_DEFAULTS[key]) as FeatureFlags[K]);
101
+ }
102
+
103
+ export function onFlagChange(key: keyof FeatureFlags, callback: (value: boolean) => void): () => void {
104
+ const listener = (flagKey: string, value: unknown) => {
105
+ if (flagKey === key) callback(value as boolean);
106
+ };
107
+ client.registerFeatureFlagListener(key, listener);
108
+ return () => client.unregisterFeatureFlagListener(key, listener);
109
+ }
110
+
111
+ export { client as ldClient };
112
+ `;
113
+ }
114
+ function generateFirebaseExpo() {
115
+ return `import remoteConfig from "@react-native-firebase/remote-config";
116
+
117
+ export type FeatureFlags = {
118
+ new_onboarding: boolean;
119
+ premium_features: boolean;
120
+ experimental_ui: boolean;
121
+ };
122
+
123
+ const FLAG_DEFAULTS: FeatureFlags = {
124
+ new_onboarding: false,
125
+ premium_features: false,
126
+ experimental_ui: false,
127
+ };
128
+
129
+ export async function initFeatureFlags(): Promise<void> {
130
+ await remoteConfig().setDefaults(FLAG_DEFAULTS as Record<string, boolean>);
131
+ await remoteConfig().setConfigSettings({
132
+ minimumFetchIntervalMillis: __DEV__ ? 0 : 3600000,
133
+ });
134
+ await remoteConfig().fetchAndActivate();
135
+ }
136
+
137
+ export function getFlag<K extends keyof FeatureFlags>(key: K): FeatureFlags[K] {
138
+ return remoteConfig().getBoolean(key) as FeatureFlags[K];
139
+ }
140
+
141
+ export function getAllFlags(): FeatureFlags {
142
+ return {
143
+ new_onboarding: remoteConfig().getBoolean("new_onboarding"),
144
+ premium_features: remoteConfig().getBoolean("premium_features"),
145
+ experimental_ui: remoteConfig().getBoolean("experimental_ui"),
146
+ };
147
+ }
148
+
149
+ export async function refreshFlags(): Promise<void> {
150
+ await remoteConfig().fetchAndActivate();
151
+ }
152
+ `;
153
+ }
154
+ function generateFlutterFeatureFlags(provider) {
155
+ const providerImport = provider === "firebase"
156
+ ? "import 'package:firebase_remote_config/firebase_remote_config.dart';"
157
+ : "// Add your feature flag provider package import here";
158
+ return `${providerImport}
159
+
160
+ enum FeatureFlag {
161
+ newOnboarding('new_onboarding', false),
162
+ premiumFeatures('premium_features', false),
163
+ experimentalUi('experimental_ui', false);
164
+
165
+ const FeatureFlag(this.key, this.defaultValue);
166
+ final String key;
167
+ final bool defaultValue;
168
+ }
169
+
170
+ class FeatureFlagService {
171
+ static final FeatureFlagService _instance = FeatureFlagService._();
172
+ factory FeatureFlagService() => _instance;
173
+ FeatureFlagService._();
174
+
175
+ final Map<String, bool> _overrides = {};
176
+
177
+ Future<void> init() async {
178
+ ${provider === "firebase" ? ` final rc = FirebaseRemoteConfig.instance;
179
+ await rc.setDefaults({
180
+ for (final flag in FeatureFlag.values) flag.key: flag.defaultValue,
181
+ });
182
+ await rc.setConfigSettings(RemoteConfigSettings(
183
+ fetchTimeout: const Duration(seconds: 10),
184
+ minimumFetchInterval: const Duration(hours: 1),
185
+ ));
186
+ await rc.fetchAndActivate();` : ` // Initialize your feature flag provider here
187
+ // Load remote flag values`}
188
+ }
189
+
190
+ bool isEnabled(FeatureFlag flag) {
191
+ if (_overrides.containsKey(flag.key)) return _overrides[flag.key]!;
192
+ ${provider === "firebase" ? ` return FirebaseRemoteConfig.instance.getBool(flag.key);` : ` return flag.defaultValue;`}
193
+ }
194
+
195
+ void setOverride(FeatureFlag flag, bool value) {
196
+ _overrides[flag.key] = value;
197
+ }
198
+
199
+ void clearOverrides() {
200
+ _overrides.clear();
201
+ }
202
+
203
+ Future<void> refresh() async {
204
+ ${provider === "firebase" ? ` await FirebaseRemoteConfig.instance.fetchAndActivate();` : ` // Refresh from remote provider`}
205
+ }
206
+ }
207
+ `;
208
+ }
209
+ export function register(server) {
210
+ server.tool("mobile_setupFeatureFlags", "Add a typed feature flag system with default values, remote sync, and provider integration (PostHog, LaunchDarkly, or Firebase Remote Config).", inputSchema, async (args) => {
211
+ try {
212
+ const root = args.project_path || process.cwd();
213
+ const outDir = join(root, args.output_directory);
214
+ mkdirSync(outDir, { recursive: true });
215
+ const isFlutter = args.framework === "flutter";
216
+ const ext = isFlutter ? "dart" : "ts";
217
+ const fileName = `feature-flags.${ext}`;
218
+ const filePath = join(outDir, fileName);
219
+ if (existsSync(filePath)) {
220
+ return errorResponse(new Error(`File already exists: ${filePath}`));
221
+ }
222
+ let content;
223
+ let dependencies;
224
+ const nextSteps = [];
225
+ if (isFlutter) {
226
+ content = generateFlutterFeatureFlags(args.provider);
227
+ dependencies = args.provider === "firebase"
228
+ ? ["firebase_remote_config"]
229
+ : [];
230
+ nextSteps.push("Call FeatureFlagService().init() in main() before runApp()", "Use FeatureFlagService().isEnabled(FeatureFlag.newOnboarding) to check flags", "Add new flags to the FeatureFlag enum as needed", `Configure ${args.provider} dashboard with matching flag keys`);
231
+ }
232
+ else {
233
+ switch (args.provider) {
234
+ case "launchdarkly":
235
+ content = generateLaunchDarklyExpo();
236
+ dependencies = ["@launchdarkly/react-native-client-sdk"];
237
+ nextSteps.push("Set EXPO_PUBLIC_LAUNCHDARKLY_KEY in .env", "Call initFeatureFlags(userId) after authentication", "Use getFlag('new_onboarding') to check flags");
238
+ break;
239
+ case "firebase":
240
+ content = generateFirebaseExpo();
241
+ dependencies = ["@react-native-firebase/remote-config", "@react-native-firebase/app"];
242
+ nextSteps.push("Ensure Firebase is configured (google-services.json / GoogleService-Info.plist)", "Call initFeatureFlags() in app root before rendering", "Use getFlag('new_onboarding') to check flags");
243
+ break;
244
+ default:
245
+ content = generatePostHogExpo();
246
+ dependencies = ["posthog-react-native"];
247
+ nextSteps.push("Set EXPO_PUBLIC_POSTHOG_KEY in .env", "Use useFeatureFlag('new_onboarding') in components", "Call identifyUser(userId) after authentication");
248
+ break;
249
+ }
250
+ }
251
+ writeFileSync(filePath, content, "utf-8");
252
+ return textResponse(JSON.stringify({
253
+ success: true,
254
+ provider: args.provider,
255
+ framework: args.framework,
256
+ file_created: filePath,
257
+ dependencies_needed: dependencies,
258
+ next_steps: nextSteps,
259
+ }, null, 2));
260
+ }
261
+ catch (err) {
262
+ return errorResponse(err);
263
+ }
264
+ });
265
+ }
266
+ //# sourceMappingURL=setupFeatureFlags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupFeatureFlags.js","sourceRoot":"","sources":["../../src/tools/setupFeatureFlags.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
3
+ //# sourceMappingURL=setupTheming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupTheming.d.ts","sourceRoot":"","sources":["../../src/tools/setupTheming.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAmQzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2EhD"}
@@ -0,0 +1,303 @@
1
+ import { z } from "zod";
2
+ import { writeFileSync, mkdirSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { textResponse, errorResponse } from "../types.js";
5
+ const inputSchema = {
6
+ project_path: z
7
+ .string()
8
+ .optional()
9
+ .describe("Absolute path to the project root. Defaults to cwd."),
10
+ framework: z
11
+ .enum(["expo", "flutter"])
12
+ .optional()
13
+ .default("expo")
14
+ .describe("Framework (default: expo)."),
15
+ output_directory: z
16
+ .string()
17
+ .optional()
18
+ .default("lib")
19
+ .describe("Output directory relative to project root (default: lib)."),
20
+ };
21
+ function generateExpoTokens() {
22
+ return `export const colors = {
23
+ light: {
24
+ primary: "#0A84FF",
25
+ primaryContainer: "#D6E4FF",
26
+ background: "#FFFFFF",
27
+ surface: "#F2F2F7",
28
+ surfaceVariant: "#E5E5EA",
29
+ text: "#000000",
30
+ textSecondary: "#3C3C43",
31
+ textTertiary: "#8E8E93",
32
+ border: "#C6C6C8",
33
+ error: "#FF3B30",
34
+ success: "#34C759",
35
+ warning: "#FF9500",
36
+ },
37
+ dark: {
38
+ primary: "#0A84FF",
39
+ primaryContainer: "#003A70",
40
+ background: "#000000",
41
+ surface: "#1C1C1E",
42
+ surfaceVariant: "#2C2C2E",
43
+ text: "#FFFFFF",
44
+ textSecondary: "#EBEBF5",
45
+ textTertiary: "#8E8E93",
46
+ border: "#38383A",
47
+ error: "#FF453A",
48
+ success: "#30D158",
49
+ warning: "#FF9F0A",
50
+ },
51
+ } as const;
52
+
53
+ export const spacing = {
54
+ xs: 4,
55
+ sm: 8,
56
+ md: 16,
57
+ lg: 24,
58
+ xl: 32,
59
+ xxl: 48,
60
+ } as const;
61
+
62
+ export const typography = {
63
+ largeTitle: { fontSize: 34, lineHeight: 41, fontWeight: "700" as const },
64
+ title1: { fontSize: 28, lineHeight: 34, fontWeight: "700" as const },
65
+ title2: { fontSize: 22, lineHeight: 28, fontWeight: "700" as const },
66
+ title3: { fontSize: 20, lineHeight: 25, fontWeight: "600" as const },
67
+ headline: { fontSize: 17, lineHeight: 22, fontWeight: "600" as const },
68
+ body: { fontSize: 17, lineHeight: 22, fontWeight: "400" as const },
69
+ callout: { fontSize: 16, lineHeight: 21, fontWeight: "400" as const },
70
+ subheadline: { fontSize: 15, lineHeight: 20, fontWeight: "400" as const },
71
+ footnote: { fontSize: 13, lineHeight: 18, fontWeight: "400" as const },
72
+ caption1: { fontSize: 12, lineHeight: 16, fontWeight: "400" as const },
73
+ caption2: { fontSize: 11, lineHeight: 13, fontWeight: "400" as const },
74
+ } as const;
75
+
76
+ export const radii = {
77
+ sm: 4,
78
+ md: 8,
79
+ lg: 12,
80
+ xl: 16,
81
+ full: 9999,
82
+ } as const;
83
+
84
+ export type ColorScheme = "light" | "dark";
85
+ export type Colors = typeof colors.light;
86
+ `;
87
+ }
88
+ function generateExpoThemeProvider() {
89
+ return `import React, { createContext, useContext, useEffect, useState } from "react";
90
+ import { useColorScheme as useSystemColorScheme } from "react-native";
91
+ import AsyncStorage from "@react-native-async-storage/async-storage";
92
+ import { colors, spacing, typography, radii, type ColorScheme, type Colors } from "./tokens";
93
+
94
+ const THEME_STORAGE_KEY = "@app_theme_preference";
95
+
96
+ interface ThemeContextValue {
97
+ colorScheme: ColorScheme;
98
+ colors: Colors;
99
+ spacing: typeof spacing;
100
+ typography: typeof typography;
101
+ radii: typeof radii;
102
+ setColorScheme: (scheme: ColorScheme | "system") => void;
103
+ isSystem: boolean;
104
+ }
105
+
106
+ const ThemeContext = createContext<ThemeContextValue | null>(null);
107
+
108
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
109
+ const systemScheme = useSystemColorScheme() ?? "light";
110
+ const [preference, setPreference] = useState<ColorScheme | "system">("system");
111
+ const [loaded, setLoaded] = useState(false);
112
+
113
+ useEffect(() => {
114
+ AsyncStorage.getItem(THEME_STORAGE_KEY).then((stored) => {
115
+ if (stored === "light" || stored === "dark" || stored === "system") {
116
+ setPreference(stored);
117
+ }
118
+ setLoaded(true);
119
+ });
120
+ }, []);
121
+
122
+ const colorScheme: ColorScheme = preference === "system" ? systemScheme : preference;
123
+
124
+ const setColorScheme = (scheme: ColorScheme | "system") => {
125
+ setPreference(scheme);
126
+ AsyncStorage.setItem(THEME_STORAGE_KEY, scheme);
127
+ };
128
+
129
+ if (!loaded) return null;
130
+
131
+ return (
132
+ <ThemeContext.Provider
133
+ value={{
134
+ colorScheme,
135
+ colors: colors[colorScheme],
136
+ spacing,
137
+ typography,
138
+ radii,
139
+ setColorScheme,
140
+ isSystem: preference === "system",
141
+ }}
142
+ >
143
+ {children}
144
+ </ThemeContext.Provider>
145
+ );
146
+ }
147
+
148
+ export function useTheme(): ThemeContextValue {
149
+ const ctx = useContext(ThemeContext);
150
+ if (!ctx) throw new Error("useTheme must be used within a ThemeProvider");
151
+ return ctx;
152
+ }
153
+ `;
154
+ }
155
+ function generateFlutterTokens() {
156
+ return `import 'package:flutter/material.dart';
157
+
158
+ abstract class AppColors {
159
+ static const light = _LightColors();
160
+ static const dark = _DarkColors();
161
+ }
162
+
163
+ class _LightColors {
164
+ const _LightColors();
165
+ Color get primary => const Color(0xFF0A84FF);
166
+ Color get primaryContainer => const Color(0xFFD6E4FF);
167
+ Color get background => const Color(0xFFFFFFFF);
168
+ Color get surface => const Color(0xFFF2F2F7);
169
+ Color get surfaceVariant => const Color(0xFFE5E5EA);
170
+ Color get onSurface => const Color(0xFF000000);
171
+ Color get onSurfaceVariant => const Color(0xFF3C3C43);
172
+ Color get outline => const Color(0xFFC6C6C8);
173
+ Color get error => const Color(0xFFFF3B30);
174
+ Color get success => const Color(0xFF34C759);
175
+ Color get warning => const Color(0xFFFF9500);
176
+ }
177
+
178
+ class _DarkColors {
179
+ const _DarkColors();
180
+ Color get primary => const Color(0xFF0A84FF);
181
+ Color get primaryContainer => const Color(0xFF003A70);
182
+ Color get background => const Color(0xFF000000);
183
+ Color get surface => const Color(0xFF1C1C1E);
184
+ Color get surfaceVariant => const Color(0xFF2C2C2E);
185
+ Color get onSurface => const Color(0xFFFFFFFF);
186
+ Color get onSurfaceVariant => const Color(0xFFEBEBF5);
187
+ Color get outline => const Color(0xFF38383A);
188
+ Color get error => const Color(0xFFFF453A);
189
+ Color get success => const Color(0xFF30D158);
190
+ Color get warning => const Color(0xFFFF9F0A);
191
+ }
192
+
193
+ abstract class AppSpacing {
194
+ static const double xs = 4;
195
+ static const double sm = 8;
196
+ static const double md = 16;
197
+ static const double lg = 24;
198
+ static const double xl = 32;
199
+ static const double xxl = 48;
200
+ }
201
+
202
+ abstract class AppRadii {
203
+ static const double sm = 4;
204
+ static const double md = 8;
205
+ static const double lg = 12;
206
+ static const double xl = 16;
207
+ static const double full = 9999;
208
+ }
209
+ `;
210
+ }
211
+ function generateFlutterTheme() {
212
+ return `import 'package:flutter/material.dart';
213
+ import 'tokens.dart';
214
+
215
+ class AppTheme {
216
+ static ThemeData light() {
217
+ final colors = AppColors.light;
218
+ return ThemeData(
219
+ useMaterial3: true,
220
+ brightness: Brightness.light,
221
+ colorScheme: ColorScheme.light(
222
+ primary: colors.primary,
223
+ primaryContainer: colors.primaryContainer,
224
+ surface: colors.surface,
225
+ surfaceContainerHighest: colors.surfaceVariant,
226
+ onSurface: colors.onSurface,
227
+ onSurfaceVariant: colors.onSurfaceVariant,
228
+ outline: colors.outline,
229
+ error: colors.error,
230
+ ),
231
+ scaffoldBackgroundColor: colors.background,
232
+ );
233
+ }
234
+
235
+ static ThemeData dark() {
236
+ final colors = AppColors.dark;
237
+ return ThemeData(
238
+ useMaterial3: true,
239
+ brightness: Brightness.dark,
240
+ colorScheme: ColorScheme.dark(
241
+ primary: colors.primary,
242
+ primaryContainer: colors.primaryContainer,
243
+ surface: colors.surface,
244
+ surfaceContainerHighest: colors.surfaceVariant,
245
+ onSurface: colors.onSurface,
246
+ onSurfaceVariant: colors.onSurfaceVariant,
247
+ outline: colors.outline,
248
+ error: colors.error,
249
+ ),
250
+ scaffoldBackgroundColor: colors.background,
251
+ );
252
+ }
253
+ }
254
+ `;
255
+ }
256
+ export function register(server) {
257
+ server.tool("mobile_setupTheming", "Initialize a design token system with light/dark themes, semantic colors, spacing, typography, and persistent theme preference.", inputSchema, async (args) => {
258
+ try {
259
+ const root = args.project_path || process.cwd();
260
+ const outDir = join(root, args.output_directory, "theme");
261
+ mkdirSync(outDir, { recursive: true });
262
+ const isFlutter = args.framework === "flutter";
263
+ const ext = isFlutter ? "dart" : "ts";
264
+ const files = [];
265
+ if (isFlutter) {
266
+ files.push({ name: `tokens.${ext}`, content: generateFlutterTokens() }, { name: `theme.${ext}`, content: generateFlutterTheme() });
267
+ }
268
+ else {
269
+ files.push({ name: `tokens.${ext}`, content: generateExpoTokens() }, { name: `theme-provider.${ext}x`, content: generateExpoThemeProvider() });
270
+ }
271
+ const created = [];
272
+ const skipped = [];
273
+ for (const file of files) {
274
+ const filePath = join(outDir, file.name);
275
+ if (existsSync(filePath)) {
276
+ skipped.push(filePath);
277
+ }
278
+ else {
279
+ writeFileSync(filePath, file.content, "utf-8");
280
+ created.push(filePath);
281
+ }
282
+ }
283
+ const nextSteps = [];
284
+ if (isFlutter) {
285
+ nextSteps.push("Use AppTheme.light() and AppTheme.dark() in MaterialApp's theme and darkTheme", "Access colors via Theme.of(context).colorScheme", "Use AppSpacing and AppRadii constants for consistent layout", "Add shared_preferences for persistent theme preference");
286
+ }
287
+ else {
288
+ nextSteps.push("Install @react-native-async-storage/async-storage for theme persistence", "Wrap your root layout with <ThemeProvider>", "Use const { colors, spacing } = useTheme() in components", "Use colorScheme for StatusBar and NavigationBar styling");
289
+ }
290
+ return textResponse(JSON.stringify({
291
+ success: true,
292
+ framework: args.framework,
293
+ files_created: created,
294
+ files_skipped: skipped,
295
+ next_steps: nextSteps,
296
+ }, null, 2));
297
+ }
298
+ catch (err) {
299
+ return errorResponse(err);
300
+ }
301
+ });
302
+ }
303
+ //# sourceMappingURL=setupTheming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupTheming.js","sourceRoot":"","sources":["../../src/tools/setupTheming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,GAAG;IAClB,YAAY,EAAE,CAAC;SACZ,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,qDAAqD,CAAC;IAClE,SAAS,EAAE,CAAC;SACT,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;SACzB,QAAQ,EAAE;SACV,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CAAC,4BAA4B,CAAC;IACzC,gBAAgB,EAAE,CAAC;SAChB,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,2DAA2D,CAAC;CACzE,CAAC;AAEF,SAAS,kBAAkB;IACzB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgER,CAAC;AACF,CAAC;AAED,SAAS,yBAAyB;IAChC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgER,CAAC;AACF,CAAC;AAED,SAAS,qBAAqB;IAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDR,CAAC;AACF,CAAC;AAED,SAAS,oBAAoB;IAC3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAiB;IACxC,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,iIAAiI,EACjI,WAAW,EACX,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;YAC1D,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAEvC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC;YAC/C,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;YACtC,MAAM,KAAK,GAA6C,EAAE,CAAC;YAE3D,IAAI,SAAS,EAAE,CAAC;gBACd,KAAK,CAAC,IAAI,CACR,EAAE,IAAI,EAAE,UAAU,GAAG,EAAE,EAAE,OAAO,EAAE,qBAAqB,EAAE,EAAE,EAC3D,EAAE,IAAI,EAAE,SAAS,GAAG,EAAE,EAAE,OAAO,EAAE,oBAAoB,EAAE,EAAE,CAC1D,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CACR,EAAE,IAAI,EAAE,UAAU,GAAG,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,EACxD,EAAE,IAAI,EAAE,kBAAkB,GAAG,GAAG,EAAE,OAAO,EAAE,yBAAyB,EAAE,EAAE,CACzE,CAAC;YACJ,CAAC;YAED,MAAM,OAAO,GAAa,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;YAE7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBACzC,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACzB,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACzB,CAAC;qBAAM,CAAC;oBACN,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBAC/C,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;YAED,MAAM,SAAS,GAAa,EAAE,CAAC;YAC/B,IAAI,SAAS,EAAE,CAAC;gBACd,SAAS,CAAC,IAAI,CACZ,+EAA+E,EAC/E,iDAAiD,EACjD,6DAA6D,EAC7D,wDAAwD,CACzD,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,SAAS,CAAC,IAAI,CACZ,yEAAyE,EACzE,4CAA4C,EAC5C,0DAA0D,EAC1D,yDAAyD,CAC1D,CAAC;YACJ,CAAC;YAED,OAAO,YAAY,CACjB,IAAI,CAAC,SAAS,CACZ;gBACE,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,aAAa,EAAE,OAAO;gBACtB,aAAa,EAAE,OAAO;gBACtB,UAAU,EAAE,SAAS;aACtB,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tmhs/mobile-mcp",
3
- "version": "0.10.0",
4
- "description": "MCP server for mobile app development - 30 tools for environment checks, project scaffolding, device deployment, screen/component generation, dependency installation, permissions, AI integration, build health, push notifications, deep links, dev environment reset, store builds, metadata validation, App Store submission, Play Store submission, screenshot capture, bundle analysis, OTA update configuration, test execution, CI/CD setup, test file generation, i18n setup, map integration, form generation, real-time client setup, security auditing, performance profiling, offline readiness checks, and APM monitoring setup.",
3
+ "version": "0.11.0",
4
+ "description": "MCP server for mobile app development - 33 tools for environment checks, project scaffolding, device deployment, screen/component generation, dependency installation, permissions, AI integration, build health, push notifications, deep links, dev environment reset, store builds, metadata validation, App Store submission, Play Store submission, screenshot capture, bundle analysis, OTA update configuration, test execution, CI/CD setup, test file generation, i18n setup, map integration, form generation, real-time client setup, security auditing, performance profiling, offline readiness checks, APM monitoring setup, theming setup, accessibility auditing, and feature flag configuration.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {