@momo-kits/mcp-expo 2.0.0-beta.1 → 2.0.0-beta.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/server.mjs +58 -1
  3. package/template/App.tsx +148 -141
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.2",
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/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,161 @@
1
- import React from 'react';
2
- import {
3
- View,
4
- Text,
5
- StyleSheet,
6
- TouchableOpacity,
7
- ScrollView,
8
- StatusBar,
9
- } from 'react-native';
10
-
11
- export default function App() {
1
+
2
+ import React, { useState } from 'react';
3
+ import { View, Text, TouchableOpacity, ScrollView, StyleSheet, ActivityIndicator, Alert } from 'react-native';
4
+
5
+ const Colors = {
6
+ primary: '#D82D8B',
7
+ danger: '#E53935',
8
+ white: '#FFFFFF',
9
+ black: '#1A1A1A',
10
+ textSecondary: '#6D6D6D',
11
+ textDisable: '#BDBDBD',
12
+ bgSurface: '#FFFFFF',
13
+ bgTonal: '#FCE4F0',
14
+ bgDisable: '#F0F0F0',
15
+ borderDefault: '#E0E0E0',
16
+ sectionBg: '#F5F5F5',
17
+ };
18
+
19
+ const SizeConfig: Record<string, any> = {
20
+ large: { height: 48, px: 16, minWidth: 128, radius: 8, fontSize: 16 },
21
+ medium: { height: 36, px: 12, minWidth: 80, radius: 8, fontSize: 14 },
22
+ small: { height: 28, px: 8, minWidth: 60, radius: 8, fontSize: 12 },
23
+ };
24
+
25
+ function getTypeStyle(type: string) {
26
+ switch (type) {
27
+ case 'primary': return { bg: Colors.primary, text: Colors.white, border: '' };
28
+ case 'tonal': return { bg: Colors.bgTonal, text: Colors.primary, border: '' };
29
+ case 'secondary': return { bg: Colors.bgSurface, text: Colors.black, border: Colors.borderDefault };
30
+ case 'outline': return { bg: Colors.bgSurface, text: Colors.primary, border: Colors.primary };
31
+ case 'text': return { bg: 'transparent', text: Colors.primary, border: '' };
32
+ case 'danger': return { bg: Colors.danger, text: Colors.white, border: '' };
33
+ case 'disabled': return { bg: Colors.bgDisable, text: Colors.textDisable, border: '' };
34
+ default: return { bg: Colors.primary, text: Colors.white, border: '' };
35
+ }
36
+ }
37
+
38
+ function KitButton(props: { title: string; type?: string; size?: string; full?: boolean; loading?: boolean; onPress?: () => void }) {
39
+ const { title = 'Button', type = 'primary', size = 'large', full = true, loading = false, onPress } = props;
40
+ const s = SizeConfig[size] || SizeConfig.large;
41
+ const t = getTypeStyle(type);
42
+ const isDisabled = type === 'disabled' || loading;
43
+
44
+ const containerStyle: any = {
45
+ height: s.height,
46
+ paddingHorizontal: s.px,
47
+ minWidth: s.minWidth,
48
+ borderRadius: s.radius,
49
+ backgroundColor: t.bg,
50
+ alignItems: 'center',
51
+ justifyContent: 'center',
52
+ flexDirection: 'row',
53
+ opacity: loading ? 0.75 : 1,
54
+ alignSelf: full ? 'stretch' : 'flex-start',
55
+ };
56
+
57
+ if (t.border) {
58
+ containerStyle.borderWidth = 1;
59
+ containerStyle.borderColor = t.border;
60
+ }
61
+
12
62
  return (
13
- <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>
25
-
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>
63
+ <TouchableOpacity activeOpacity={0.7} disabled={isDisabled} onPress={onPress} style={containerStyle}>
64
+ {loading && <ActivityIndicator size="small" color={t.text} style={{ marginRight: 8 }} />}
65
+ <Text style={{ color: t.text, fontSize: s.fontSize, fontWeight: '700' }}>{title}</Text>
66
+ </TouchableOpacity>
67
+ );
68
+ }
33
69
 
34
- <View style={styles.buttonGroup}>
35
- <TouchableOpacity style={styles.button} activeOpacity={0.7}>
36
- <Text style={styles.buttonText}>Primary</Text>
37
- </TouchableOpacity>
38
- <TouchableOpacity
39
- style={[styles.button, styles.buttonOutline]}
40
- activeOpacity={0.7}
41
- >
42
- <Text style={[styles.buttonText, styles.buttonTextOutline]}>
43
- Secondary
44
- </Text>
45
- </TouchableOpacity>
46
- </View>
70
+ function App() {
71
+ const [loadingId, setLoadingId] = useState('');
72
+
73
+ const simulateLoading = (id: string) => {
74
+ setLoadingId(id);
75
+ setTimeout(() => setLoadingId(''), 2000);
76
+ };
77
+
78
+ const types = ['primary', 'tonal', 'secondary', 'outline', 'text', 'danger', 'disabled'];
79
+ const sizes = ['large', 'medium', 'small'];
80
+
81
+ return (
82
+ <ScrollView style={{ flex: 1, backgroundColor: '#FFF' }} contentContainerStyle={{ padding: 20, paddingTop: 60 }}>
83
+ <Text style={{ fontSize: 28, fontWeight: '800', color: Colors.black, marginBottom: 4 }}>Button</Text>
84
+ <Text style={{ fontSize: 14, color: Colors.textSecondary, marginBottom: 24 }}>@momo-kits/foundation</Text>
47
85
 
48
- <View style={styles.statusRow}>
49
- <View style={styles.dot} />
50
- <Text style={styles.statusText}>Metro ready · HMR enabled</Text>
86
+ <Text style={styles.sectionTitle}>TYPES</Text>
87
+ <View style={styles.section}>
88
+ {types.map((type) => (
89
+ <View key={type} style={{ marginBottom: 10 }}>
90
+ <KitButton title={type.charAt(0).toUpperCase() + type.slice(1)} type={type} onPress={() => Alert.alert('Pressed', type)} />
91
+ </View>
92
+ ))}
93
+ </View>
94
+
95
+ <Text style={styles.sectionTitle}>SIZES</Text>
96
+ <View style={styles.section}>
97
+ {sizes.map((size) => (
98
+ <View key={size} style={{ marginBottom: 10 }}>
99
+ <KitButton title={size + ' (' + SizeConfig[size].height + 'px)'} size={size} onPress={() => Alert.alert('Size', size)} />
100
+ </View>
101
+ ))}
102
+ </View>
103
+
104
+ <Text style={styles.sectionTitle}>WIDTH</Text>
105
+ <View style={styles.section}>
106
+ <View style={{ marginBottom: 10 }}>
107
+ <KitButton title="Full Width (default)" full={true} onPress={() => {}} />
51
108
  </View>
52
- </ScrollView>
53
- </View>
109
+ <KitButton title="Auto Width" full={false} onPress={() => {}} />
110
+ </View>
111
+
112
+ <Text style={styles.sectionTitle}>LOADING STATE</Text>
113
+ <View style={styles.section}>
114
+ {['primary', 'secondary', 'danger'].map((type) => (
115
+ <View key={type} style={{ marginBottom: 10 }}>
116
+ <KitButton
117
+ title={loadingId === type ? 'Loading...' : 'Tap to load (' + type + ')'}
118
+ type={type}
119
+ loading={loadingId === type}
120
+ onPress={() => simulateLoading(type)}
121
+ />
122
+ </View>
123
+ ))}
124
+ </View>
125
+
126
+ <Text style={styles.sectionTitle}>SIZE x TYPE</Text>
127
+ <View style={styles.section}>
128
+ {sizes.map((size) => (
129
+ <View key={size}>
130
+ <Text style={{ fontSize: 12, fontWeight: '600', color: Colors.textSecondary, marginBottom: 6 }}>{size}</Text>
131
+ <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 12 }}>
132
+ {['primary', 'tonal', 'outline', 'danger'].map((type) => (
133
+ <KitButton key={size + type} title={type.slice(0, 3).toUpperCase()} type={type} size={size} full={false} onPress={() => {}} />
134
+ ))}
135
+ </View>
136
+ </View>
137
+ ))}
138
+ </View>
139
+
140
+ <View style={{ height: 48 }} />
141
+ </ScrollView>
54
142
  );
55
143
  }
56
144
 
145
+ export default App;
146
+
57
147
  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,
86
- },
87
- bold: {
88
- fontWeight: '600',
89
- color: '#111111',
90
- },
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,
148
+ sectionTitle: {
149
+ fontSize: 13,
105
150
  fontWeight: '600',
106
- color: '#111111',
107
- marginBottom: 8,
108
- },
109
- cardText: {
110
- fontSize: 14,
111
- color: '#666666',
112
- lineHeight: 20,
113
- },
114
- buttonGroup: {
115
- flexDirection: 'row',
116
- gap: 12,
117
- marginBottom: 32,
151
+ color: '#6D6D6D',
152
+ letterSpacing: 0.5,
153
+ marginTop: 24,
154
+ marginBottom: 12,
118
155
  },
119
- button: {
120
- flex: 1,
121
- backgroundColor: '#111111',
156
+ section: {
157
+ backgroundColor: '#F5F5F5',
122
158
  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',
159
+ padding: 16,
153
160
  },
154
161
  });