@momo-kits/mcp-expo 2.0.0-beta.1 → 2.0.0-beta.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/mcp-expo",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.3",
4
4
  "description": "MCP server: push AI-generated code to Expo and get LAN URL for Expo Go demo",
5
5
  "type": "module",
6
6
  "main": "server.mjs",
package/remote-server.mjs CHANGED
@@ -97,8 +97,23 @@ async function ensureDeps() {
97
97
  }
98
98
  }
99
99
 
100
+ async function killPort(port) {
101
+ try {
102
+ const { execSync } = await import('child_process');
103
+ const pids = execSync(`lsof -ti:${port} 2>/dev/null`, { encoding: 'utf8' }).trim();
104
+ if (pids) {
105
+ for (const pid of pids.split('\n')) {
106
+ try { process.kill(parseInt(pid), 'SIGTERM'); } catch {}
107
+ }
108
+ log(`Killed existing processes on port ${port}: ${pids.replace(/\n/g, ', ')}`);
109
+ await new Promise(r => setTimeout(r, 1000));
110
+ }
111
+ } catch {}
112
+ }
113
+
100
114
  async function startExpoMetro() {
101
115
  await ensureDeps();
116
+ await killPort(8081);
102
117
 
103
118
  return new Promise((resolve, reject) => {
104
119
  let settled = false;
@@ -112,7 +127,7 @@ async function startExpoMetro() {
112
127
 
113
128
  log(`Starting Expo Metro in ${TEMPLATE_DIR}...`);
114
129
 
115
- metroProcess = spawn('npx', ['expo', 'start', '--lan', '--port', '8081'], {
130
+ metroProcess = spawn('npx', ['expo', 'start', '--tunnel', '--port', '8081'], {
116
131
  cwd: TEMPLATE_DIR,
117
132
  stdio: ['pipe', 'pipe', 'pipe'],
118
133
  env: { ...process.env, FORCE_COLOR: '0' },
package/server.mjs CHANGED
@@ -125,9 +125,66 @@ async function handleGetStatus() {
125
125
 
126
126
  // ── MCP Server ─────────────────────────────────────────────────────────────────
127
127
 
128
+ const SERVER_INSTRUCTIONS = `
129
+ You are connected to the mcp-expo server — a tool for live-previewing React Native UI on a real device via Expo Go.
130
+
131
+ ## How to use push_screen_and_run
132
+
133
+ The "code" field must be a **complete, self-contained App.tsx file**. Follow these rules:
134
+
135
+ 1. **Default export required**: The file must \`export default\` a React component.
136
+ 2. **Only use built-in React Native + Expo packages**:
137
+ - react, react-native (View, Text, ScrollView, TouchableOpacity, StyleSheet, Image, TextInput, FlatList, etc.)
138
+ - expo-status-bar, expo-linear-gradient, expo-constants
139
+ - Do NOT import packages that aren't pre-installed (no react-navigation, no external UI libs).
140
+ 3. **Inline all styles**: Use StyleSheet.create() at the bottom of the file. No external style files.
141
+ 4. **Inline all assets**: Use emoji, Unicode, or remote image URLs. No local asset imports.
142
+ 5. **No multi-file**: Everything must be in one single file. Define all sub-components in the same file.
143
+ 6. **TypeScript OK**: The file is App.tsx, TypeScript is supported.
144
+
145
+ ## Example skeleton
146
+
147
+ \`\`\`tsx
148
+ import React from 'react';
149
+ import { View, Text, StyleSheet, ScrollView, StatusBar } from 'react-native';
150
+
151
+ export default function App() {
152
+ return (
153
+ <View style={styles.container}>
154
+ <StatusBar barStyle="dark-content" />
155
+ <ScrollView contentContainerStyle={styles.content}>
156
+ <Text style={styles.title}>Hello World</Text>
157
+ </ScrollView>
158
+ </View>
159
+ );
160
+ }
161
+
162
+ const styles = StyleSheet.create({
163
+ container: { flex: 1, backgroundColor: '#fff' },
164
+ content: { padding: 24, paddingTop: 60 },
165
+ title: { fontSize: 24, fontWeight: '700' },
166
+ });
167
+ \`\`\`
168
+
169
+ ## Workflow
170
+ 1. Generate the full App.tsx code following the rules above.
171
+ 2. Call push_screen_and_run with the code.
172
+ 3. The app will reload on the user's device via HMR. Share the expo URL if it's the first run.
173
+ 4. If the user wants changes, update the code and call push_screen_and_run again (no need for reset=true unless there's a crash).
174
+
175
+ ## Common mistakes to avoid
176
+ - Do NOT use \`import\` for local files or uninstalled packages — this will crash Metro.
177
+ - Do NOT forget \`export default\`.
178
+ - Do NOT use \`require()\` for images — use URL strings instead.
179
+ - Do NOT send partial code or snippets — always send the complete file.
180
+ `.trim();
181
+
128
182
  const server = new Server(
129
183
  { name: 'mcp-expo', version: '2.0.0' },
130
- { capabilities: { tools: {} } }
184
+ {
185
+ capabilities: { tools: {} },
186
+ instructions: SERVER_INSTRUCTIONS,
187
+ }
131
188
  );
132
189
 
133
190
  const tools = [
package/template/App.tsx CHANGED
@@ -1,154 +1,175 @@
1
- import React from 'react';
1
+
2
+ import React, { useState } from 'react';
2
3
  import {
3
4
  View,
4
5
  Text,
5
6
  StyleSheet,
6
- TouchableOpacity,
7
7
  ScrollView,
8
8
  StatusBar,
9
+ TouchableOpacity,
10
+ Platform,
9
11
  } from 'react-native';
10
12
 
13
+ const Typography: Record<string, { fontSize: number; fontWeight: string; lineHeight: number; textDecorationLine?: string }> = {
14
+ headline_default_bold: { fontSize: 28, fontWeight: '700', lineHeight: 36 },
15
+ header_m_bold: { fontSize: 22, fontWeight: '700', lineHeight: 28 },
16
+ header_default_bold: { fontSize: 20, fontWeight: '700', lineHeight: 26 },
17
+ header_s_semibold: { fontSize: 18, fontWeight: '600', lineHeight: 24 },
18
+ header_xs_semibold: { fontSize: 16, fontWeight: '600', lineHeight: 22 },
19
+ body_default_regular: { fontSize: 16, fontWeight: '400', lineHeight: 24 },
20
+ body_default_regularstrikethrought: { fontSize: 16, fontWeight: '400', lineHeight: 24, textDecorationLine: 'line-through' },
21
+ description_default_regular: { fontSize: 14, fontWeight: '400', lineHeight: 20 },
22
+ description_default_regularstrikethrought: { fontSize: 14, fontWeight: '400', lineHeight: 20, textDecorationLine: 'line-through' },
23
+ description_xs_regular: { fontSize: 12, fontWeight: '400', lineHeight: 18 },
24
+ description_xs_regularstrikethrought: { fontSize: 12, fontWeight: '400', lineHeight: 18, textDecorationLine: 'line-through' },
25
+ label_default_medium: { fontSize: 16, fontWeight: '500', lineHeight: 22 },
26
+ label_s_medium: { fontSize: 14, fontWeight: '500', lineHeight: 20 },
27
+ label_xs_medium: { fontSize: 12, fontWeight: '500', lineHeight: 18 },
28
+ action_default_bold: { fontSize: 16, fontWeight: '700', lineHeight: 22 },
29
+ action_s_bold: { fontSize: 14, fontWeight: '700', lineHeight: 20 },
30
+ action_xs_bold: { fontSize: 12, fontWeight: '700', lineHeight: 18 },
31
+ action_xxs_bold: { fontSize: 10, fontWeight: '700', lineHeight: 16 },
32
+ };
33
+
34
+ const TextColors = [
35
+ { name: 'default', color: '#1A1A1A' },
36
+ { name: 'secondary', color: '#7A7A7A' },
37
+ { name: 'disable', color: '#B8B8B8' },
38
+ { name: 'primary', color: '#D82D8B' },
39
+ { name: 'success', color: '#00C853' },
40
+ { name: 'warning', color: '#FF9100' },
41
+ { name: 'error', color: '#FF1744' },
42
+ ];
43
+
44
+ type Tab = 'typography' | 'color';
45
+
11
46
  export default function App() {
47
+ const [activeTab, setActiveTab] = useState<Tab>('typography');
48
+
12
49
  return (
13
50
  <View style={styles.container}>
14
- <StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
15
- <ScrollView
16
- contentContainerStyle={styles.content}
17
- showsVerticalScrollIndicator={false}
18
- >
19
- <Text style={styles.logo}>🍎</Text>
20
- <Text style={styles.title}>MCP Expo</Text>
21
- <Text style={styles.subtitle}>
22
- AI-generated code will replace this file via the{' '}
23
- <Text style={styles.bold}>push_screen_and_run</Text> MCP tool.
24
- </Text>
51
+ <StatusBar barStyle="dark-content" />
52
+ <View style={styles.header}>
53
+ <Text style={styles.title}>Text</Text>
54
+ <Text style={styles.subtitle}>@momo-kits/foundation</Text>
55
+ </View>
25
56
 
26
- <View style={styles.card}>
27
- <Text style={styles.cardTitle}>Quick Test</Text>
28
- <Text style={styles.cardText}>
29
- This is a working Expo template. When the MCP server writes new code,
30
- it overwrites this App.tsx and the app reloads via HMR.
31
- </Text>
32
- </View>
33
-
34
- <View style={styles.buttonGroup}>
35
- <TouchableOpacity style={styles.button} activeOpacity={0.7}>
36
- <Text style={styles.buttonText}>Primary</Text>
37
- </TouchableOpacity>
57
+ <View style={styles.tabRow}>
58
+ {(['typography', 'color'] as Tab[]).map(tab => (
38
59
  <TouchableOpacity
39
- style={[styles.button, styles.buttonOutline]}
40
- activeOpacity={0.7}
60
+ key={tab}
61
+ style={[styles.tab, activeTab === tab && styles.tabActive]}
62
+ onPress={() => setActiveTab(tab)}
41
63
  >
42
- <Text style={[styles.buttonText, styles.buttonTextOutline]}>
43
- Secondary
64
+ <Text style={[styles.tabText, activeTab === tab && styles.tabTextActive]}>
65
+ {tab.charAt(0).toUpperCase() + tab.slice(1)}
44
66
  </Text>
45
67
  </TouchableOpacity>
46
- </View>
68
+ ))}
69
+ </View>
47
70
 
48
- <View style={styles.statusRow}>
49
- <View style={styles.dot} />
50
- <Text style={styles.statusText}>Metro ready · HMR enabled</Text>
51
- </View>
71
+ <ScrollView contentContainerStyle={styles.content}>
72
+ {activeTab === 'typography' && (
73
+ <View>
74
+ <Text style={styles.sectionTitle}>Typography Scale</Text>
75
+ {Object.entries(Typography).map(([name, style]) => (
76
+ <View key={name} style={styles.typoCard}>
77
+ <Text
78
+ style={{
79
+ fontSize: style.fontSize,
80
+ fontWeight: style.fontWeight as any,
81
+ lineHeight: style.lineHeight,
82
+ textDecorationLine: (style.textDecorationLine as any) || 'none',
83
+ color: '#1A1A1A',
84
+ }}
85
+ >
86
+ {name}
87
+ </Text>
88
+ <Text style={styles.meta}>
89
+ {style.fontSize}px / {style.fontWeight} / LH {style.lineHeight}
90
+ {style.textDecorationLine ? ' / strikethrough' : ''}
91
+ </Text>
92
+ </View>
93
+ ))}
94
+ </View>
95
+ )}
96
+
97
+ {activeTab === 'color' && (
98
+ <View>
99
+ <Text style={styles.sectionTitle}>Text Colors</Text>
100
+ {TextColors.map(item => (
101
+ <View key={item.name} style={styles.colorCard}>
102
+ <View style={styles.colorRow}>
103
+ <View style={[styles.colorDot, { backgroundColor: item.color }]} />
104
+ <View style={styles.colorInfo}>
105
+ <Text style={[styles.colorSample, { color: item.color }]}>
106
+ The quick brown fox jumps over the lazy dog
107
+ </Text>
108
+ <Text style={styles.colorMeta}>
109
+ {item.name} — {item.color}
110
+ </Text>
111
+ </View>
112
+ </View>
113
+ </View>
114
+ ))}
115
+ </View>
116
+ )}
52
117
  </ScrollView>
53
118
  </View>
54
119
  );
55
120
  }
56
121
 
57
122
  const styles = StyleSheet.create({
58
- container: {
59
- flex: 1,
60
- backgroundColor: '#f5f5f5',
61
- },
62
- content: {
63
- flexGrow: 1,
64
- paddingHorizontal: 24,
65
- paddingTop: 48,
66
- paddingBottom: 40,
67
- alignItems: 'center',
68
- },
69
- logo: {
70
- fontSize: 64,
71
- marginBottom: 16,
72
- },
73
- title: {
74
- fontSize: 28,
75
- fontWeight: '700',
76
- color: '#111111',
77
- marginBottom: 8,
78
- },
79
- subtitle: {
80
- fontSize: 15,
81
- color: '#666666',
82
- textAlign: 'center',
83
- lineHeight: 22,
84
- marginBottom: 32,
85
- paddingHorizontal: 8,
123
+ container: { flex: 1, backgroundColor: '#F8F8F8' },
124
+ header: {
125
+ paddingTop: Platform.OS === 'ios' ? 60 : 48,
126
+ paddingHorizontal: 20,
127
+ paddingBottom: 12,
128
+ backgroundColor: '#fff',
129
+ borderBottomWidth: StyleSheet.hairlineWidth,
130
+ borderBottomColor: '#E0E0E0',
131
+ },
132
+ title: { fontSize: 28, fontWeight: '700', color: '#1A1A1A' },
133
+ subtitle: { fontSize: 13, color: '#999', marginTop: 2 },
134
+ tabRow: {
135
+ flexDirection: 'row',
136
+ backgroundColor: '#fff',
137
+ paddingHorizontal: 16,
138
+ paddingBottom: 8,
139
+ gap: 8,
86
140
  },
87
- bold: {
141
+ tab: {
142
+ paddingVertical: 8,
143
+ paddingHorizontal: 16,
144
+ borderRadius: 20,
145
+ backgroundColor: '#F0F0F0',
146
+ },
147
+ tabActive: { backgroundColor: '#D82D8B' },
148
+ tabText: { fontSize: 14, fontWeight: '500', color: '#666' },
149
+ tabTextActive: { color: '#fff' },
150
+ content: { padding: 16, paddingBottom: 40 },
151
+ sectionTitle: {
152
+ fontSize: 18,
88
153
  fontWeight: '600',
89
- color: '#111111',
154
+ color: '#1A1A1A',
155
+ marginBottom: 12,
90
156
  },
91
- card: {
92
- width: '100%',
93
- backgroundColor: '#ffffff',
94
- borderRadius: 16,
95
- padding: 20,
96
- marginBottom: 24,
97
- shadowColor: '#000',
98
- shadowOffset: { width: 0, height: 2 },
99
- shadowOpacity: 0.06,
100
- shadowRadius: 8,
101
- elevation: 2,
102
- },
103
- cardTitle: {
104
- fontSize: 16,
105
- fontWeight: '600',
106
- color: '#111111',
157
+ typoCard: {
158
+ backgroundColor: '#fff',
159
+ borderRadius: 12,
160
+ padding: 16,
107
161
  marginBottom: 8,
108
162
  },
109
- cardText: {
110
- fontSize: 14,
111
- color: '#666666',
112
- lineHeight: 20,
113
- },
114
- buttonGroup: {
115
- flexDirection: 'row',
116
- gap: 12,
117
- marginBottom: 32,
118
- },
119
- button: {
120
- flex: 1,
121
- backgroundColor: '#111111',
163
+ meta: { fontSize: 11, color: '#999', marginTop: 6 },
164
+ colorCard: {
165
+ backgroundColor: '#fff',
122
166
  borderRadius: 12,
123
- paddingVertical: 14,
124
- alignItems: 'center',
125
- },
126
- buttonOutline: {
127
- backgroundColor: 'transparent',
128
- borderWidth: 1.5,
129
- borderColor: '#111111',
130
- },
131
- buttonText: {
132
- fontSize: 15,
133
- fontWeight: '600',
134
- color: '#ffffff',
135
- },
136
- buttonTextOutline: {
137
- color: '#111111',
138
- },
139
- statusRow: {
140
- flexDirection: 'row',
141
- alignItems: 'center',
142
- gap: 8,
143
- },
144
- dot: {
145
- width: 8,
146
- height: 8,
147
- borderRadius: 4,
148
- backgroundColor: '#22c55e',
149
- },
150
- statusText: {
151
- fontSize: 13,
152
- color: '#888888',
167
+ padding: 16,
168
+ marginBottom: 8,
153
169
  },
170
+ colorRow: { flexDirection: 'row', alignItems: 'center' },
171
+ colorDot: { width: 32, height: 32, borderRadius: 16, marginRight: 12 },
172
+ colorInfo: { flex: 1 },
173
+ colorSample: { fontSize: 15, fontWeight: '500', lineHeight: 22 },
174
+ colorMeta: { fontSize: 11, color: '#999', marginTop: 4 },
154
175
  });