@momo-kits/mcp-expo 1.0.1 → 1.0.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": "1.0.1",
3
+ "version": "1.0.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/server.mjs CHANGED
@@ -23,8 +23,12 @@ import { fileURLToPath } from 'url';
23
23
 
24
24
  // ── Config ───────────────────────────────────────────────────────────────────
25
25
 
26
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
- const TEMPLATE_DIR = path.join(__dirname, 'template');
26
+ // Resolve template dir:
27
+ // 1. MCP_EXPO_TEMPLATE_DIR env var (absolute path, highest priority)
28
+ // 2. Relative to this file: ./template (works for local dev and npm install)
29
+ // When installed globally via npx, this resolves to the user's local install path
30
+ const TEMPLATE_DIR = process.env.MCP_EXPO_TEMPLATE_DIR
31
+ || path.join(path.dirname(fileURLToPath(import.meta.url)), 'template');
28
32
  const APP_FILE = path.join(TEMPLATE_DIR, 'App.tsx');
29
33
  const METRO_START_TIMEOUT_MS = 90_000;
30
34
 
@@ -33,6 +37,7 @@ const METRO_START_TIMEOUT_MS = 90_000;
33
37
  let metroProcess = null;
34
38
  let expoUrl = null;
35
39
  let isExpoRunning = false;
40
+ let isShuttingDown = false;
36
41
 
37
42
  // ── Helpers ───────────────────────────────────────────────────────────────────
38
43
 
@@ -159,19 +164,26 @@ async function startExpoMetro() {
159
164
  settled = true;
160
165
  clearTimeout(timer);
161
166
  reject(new Error(`Metro exited unexpectedly (code ${code}).`));
167
+ } else {
168
+ // Metro crashed after startup — reset state so next call restarts cleanly
169
+ isExpoRunning = false;
170
+ log(`Metro exited (code ${code}) — state reset.`);
162
171
  }
163
172
  });
164
173
  });
165
174
  }
166
175
 
167
176
  function stopMetro() {
168
- if (metroProcess) {
169
- metroProcess.kill('SIGTERM');
170
- metroProcess = null;
171
- isExpoRunning = false;
172
- expoUrl = null;
173
- log('Metro stopped.');
174
- }
177
+ if (!metroProcess) return;
178
+ metroProcess.kill('SIGTERM');
179
+ metroProcess = null;
180
+ isExpoRunning = false;
181
+ expoUrl = null;
182
+ log('Metro stopped.');
183
+ }
184
+
185
+ function isMetroAlive() {
186
+ return metroProcess !== null && metroProcess.exitCode === null && !metroProcess.killed;
175
187
  }
176
188
 
177
189
  // ── Tool Implementations ──────────────────────────────────────────────────────
@@ -180,7 +192,7 @@ async function handlePushScreen({ code, reset = false }) {
180
192
  try {
181
193
  writeAppTsx(code);
182
194
 
183
- if (reset || !metroProcess || metroProcess.exitCode !== null) {
195
+ if (reset || !isMetroAlive()) {
184
196
  stopMetro();
185
197
  await startExpoMetro();
186
198
  isExpoRunning = true;
@@ -223,7 +235,7 @@ async function handleStopExpo() {
223
235
  }
224
236
 
225
237
  async function handleGetStatus() {
226
- const running = isExpoRunning && metroProcess !== null && metroProcess.exitCode === null;
238
+ const running = isMetroAlive();
227
239
  return {
228
240
  content: [
229
241
  {
@@ -295,6 +307,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
295
307
  };
296
308
  });
297
309
 
310
+ // ── Graceful shutdown ─────────────────────────────────────────────────────────
311
+
312
+ process.on('SIGTERM', () => {
313
+ if (isShuttingDown) return;
314
+ isShuttingDown = true;
315
+ log('SIGTERM received — shutting down.');
316
+ stopMetro();
317
+ process.exit(0);
318
+ });
319
+
320
+ process.on('SIGINT', () => {
321
+ if (isShuttingDown) return;
322
+ isShuttingDown = true;
323
+ log('SIGINT received — shutting down.');
324
+ stopMetro();
325
+ process.exit(0);
326
+ });
327
+
298
328
  // ── Main ─────────────────────────────────────────────────────────────────────
299
329
 
300
330
  async function main() {
@@ -1,8 +0,0 @@
1
- > Why do I have a folder named ".expo" in my project?
2
- The ".expo" folder is created when an Expo project is started using "expo start" command.
3
- > What do the files contain?
4
- - "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
5
- - "settings.json": contains the server configuration that is used to serve the application manifest.
6
- > Should I commit the ".expo" folder?
7
- No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
8
- Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
@@ -1,3 +0,0 @@
1
- {
2
- "devices": []
3
- }
package/template/App.tsx DELETED
@@ -1,147 +0,0 @@
1
- import React, {useContext} from 'react';
2
- import {ApplicationContext, Button, IconSources} from '@momo-kits/foundation';
3
- import {Alert, View} from 'react-native';
4
- import DemoScreen from '../../template/DemoScreen';
5
- import {ItemPickerProps} from '../../template/BottomSheetPicker';
6
-
7
- const iconData: ItemPickerProps[] = Object.keys(IconSources).map(item => {
8
- return {
9
- id: item,
10
- value: item,
11
- title: item,
12
- };
13
- });
14
-
15
- const ButtonUsage: React.FC<any> = props => {
16
- const {navigator} = useContext(ApplicationContext);
17
- const preview = {
18
- title: {
19
- title: 'Title',
20
- value: [
21
- {
22
- value: 'Open',
23
- props: {
24
- onPress: () => {
25
- navigator?.push({
26
- options: {title: 'Test'},
27
- screen: () => (
28
- <View style={{flex: 1, backgroundColor: 'red'}} />
29
- ),
30
- });
31
- },
32
- },
33
- },
34
- ],
35
- },
36
- type: {
37
- title: 'Type',
38
- value: [
39
- 'primary',
40
- 'tonal',
41
- 'secondary',
42
- 'outline',
43
- 'text',
44
- {
45
- value: 'danger',
46
- props: {
47
- onPress: () => {
48
- console.log('ABC');
49
- },
50
- },
51
- },
52
- {
53
- value: 'disabled',
54
- props: {
55
- onPress: () => {
56
- Alert.alert('ABC');
57
- },
58
- },
59
- },
60
- ],
61
- },
62
- size: {
63
- title: 'Size',
64
- value: ['large', 'medium', 'small'],
65
- },
66
- loading: {
67
- title: 'Loading',
68
- value: [true],
69
- },
70
- iconRight: {
71
- title: 'Icon right',
72
- value: [
73
- 'https://img.mservice.com.vn/momo_app_v2/new_version/img/appx_icon/16_arrow_arrow_bold_reply_all.png',
74
- ],
75
- },
76
- iconLeft: {
77
- title: 'Icon left',
78
- value: [
79
- {
80
- value:
81
- 'https://img.mservice.io/momo_app_v2/new_version/img/appx_image/ic_common_logo_momo.png',
82
- props: {
83
- useTintColor: false,
84
- },
85
- },
86
- ],
87
- },
88
- };
89
- const params = {
90
- component: Button,
91
- props: {
92
- title: {
93
- value: 'Button',
94
- type: 'string',
95
- },
96
- type: {
97
- value: 'primary',
98
- type: 'enum',
99
- data: [
100
- 'primary',
101
- 'secondary',
102
- 'tonal',
103
- 'danger',
104
- 'outline',
105
- 'text',
106
- 'disabled',
107
- ],
108
- },
109
- size: {
110
- value: 'small',
111
- type: 'enum',
112
- data: ['small', 'medium', 'large'],
113
- },
114
- full: {
115
- value: true,
116
- type: 'bool',
117
- },
118
- iconRight: {
119
- type: 'options',
120
- data: iconData,
121
- },
122
- iconLeft: {
123
- type: 'options',
124
- data: iconData,
125
- },
126
- loading: {
127
- value: true,
128
- type: 'bool',
129
- },
130
- useTintColor: {
131
- value: true,
132
- type: 'bool',
133
- },
134
- },
135
- };
136
-
137
- return (
138
- <DemoScreen
139
- component={Button}
140
- preview={preview}
141
- playground={params}
142
- {...props}
143
- />
144
- );
145
- };
146
-
147
- export default ButtonUsage;
package/template/app.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "expo": {
3
- "name": "Expo Demo Template",
4
- "slug": "expo-demo-template",
5
- "version": "1.0.0",
6
- "orientation": "portrait",
7
- "userInterfaceStyle": "automatic",
8
- "assetBundlePatterns": ["**/*"]
9
- }
10
- }