@gxp-dev/tools 2.0.81 → 2.0.83

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/README.md CHANGED
@@ -93,20 +93,24 @@ It does **not** overwrite your source files (`src/`, `theme-layouts/`, etc.).
93
93
  - **Config linting** — AJV-based JSON Schema validation of `configuration.json` (form-builder definitions) and `app-manifest.json` (plugin metadata + defaults).
94
94
  - **Pre-commit hook** — `.githooks/pre-commit` runs Prettier, ESLint, and the GxP linter on staged files; configured automatically via the `prepare` npm script.
95
95
  - **Unit testing** — Vitest + `@vue/test-utils` wired out of the box; scaffolded tests via the MCP server.
96
- - **MCP server** — 29 tools for AI coding assistants (see below).
96
+ - **MCP server** — 33 tools for AI coding assistants (see below).
97
97
  - **AI scaffolding** — pre-wired configs for Claude Code, Codex, and Gemini during `init`.
98
98
 
99
99
  ## MCP Server for AI assistants
100
100
 
101
- The toolkit ships `gxp-api-server` (bin `@gxp-dev/tools/mcp/gxp-api-server.js`), an MCP server exposing 29 tools across five families. Point your AI assistant at it to get API-aware, schema-aware, test-aware help inside plugin projects:
101
+ The toolkit ships `mcp-serve` (bin `@gxp-dev/tools/mcp/mcp-serve.js`), an MCP server exposing 33 tools across seven families. It speaks MCP over stdio via the official `@modelcontextprotocol/sdk` `StdioServerTransport`. Point your AI assistant at it to get API-aware, schema-aware, test-aware help inside plugin projects:
102
102
 
103
103
  | Family | Tools |
104
104
  | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
105
105
  | **API spec** (6) | `get_openapi_spec`, `get_asyncapi_spec`, `search_api_endpoints`, `search_websocket_events`, `get_endpoint_details`, `get_api_environment` |
106
- | **Extended API** (5) | `api_list_tags`, `api_list_operation_ids`, `api_get_operation_parameters`, `api_find_endpoints_by_schema`, `api_generate_dependency` |
106
+ | **Extended API** (7) | `api_list_tags`, `api_list_operation_ids`, `api_get_operation_parameters`, `api_find_endpoints_by_schema`, `api_find_events_for_operation`, `api_list_events`, `api_generate_dependency` |
107
107
  | **Config editor** (13) | `config_validate`, `config_list_field_types`, `config_list_card_types`, `config_get_field_schema`, `config_list_cards`, `config_list_fields`, `config_add_field`, `config_move_field`, `config_remove_field`, `config_add_card`, `config_move_card`, `config_remove_card`, `config_extract_strings` |
108
108
  | **Docs search** (3) | `docs_list_pages`, `docs_search`, `docs_get_page` — full-text across `docs.gxp.dev` via its sitemap |
109
109
  | **Test helpers** (2) | `test_scaffold_component_test`, `test_api_route` |
110
+ | **Data models** (1) | `describe_data_models` — enumerate or detail OpenAPI `components.schemas` with $ref + allOf flattening |
111
+ | **UIKit** (1) | `list_uikit_components` — list components exported by `@gxp-dev/uikit` installed in the current project |
112
+
113
+ The previous bin name `gxp-api-server` still ships as a deprecation shim — it prints a stderr notice and forwards to the same server. Update your `.mcp.json` / `.gemini/settings.json` to use `mcp-serve` going forward.
110
114
 
111
115
  Every config mutation is linter-guarded: invalid writes are refused unless `force: true`, so AI agents can't save broken state.
112
116
 
package/bin/lib/cli.js CHANGED
@@ -383,8 +383,10 @@ const cli = yargs
383
383
  .epilog(
384
384
  [
385
385
  "Docs: https://docs.gxp.dev",
386
- "AI/MCP: the gxp-api MCP server (bin: gxp-api-server) exposes 29 tools",
387
- " across API specs, config editing, docs search, and test helpers.",
386
+ "AI/MCP: the gxp-api MCP server (bin: mcp-serve) exposes 33 tools across",
387
+ " API specs, config editing, docs search, test helpers, data",
388
+ " models, and UIKit components. (gxp-api-server still works as a",
389
+ " deprecation shim.)",
388
390
  ].join("\n"),
389
391
  )
390
392
  .help("h")
@@ -180,8 +180,9 @@ function copyBundleFiles(projectPath, paths, overwrite = false) {
180
180
  },
181
181
  // Canonical project-scoped MCP config files. The AI CLIs each look for
182
182
  // their own file at the project root and auto-load the `gxp-api` server
183
- // from there. The `gxp-api-server` binary is installed on PATH by
184
- // @gxp-dev/tools, so no extra install step is required.
183
+ // from there. The `mcp-serve` binary is installed on PATH by
184
+ // @gxp-dev/tools, so no extra install step is required. (The legacy
185
+ // `gxp-api-server` name still works as a deprecation shim.)
185
186
  {
186
187
  src: "mcp.json",
187
188
  dest: ".mcp.json",
@@ -973,10 +973,10 @@ function buildInteractiveInitialPrompt(projectName, description, provider) {
973
973
 
974
974
  const mcpFixCommand =
975
975
  {
976
- claude: "claude mcp add gxp-api gxp-api-server",
977
- codex: "codex mcp add gxp-api gxp-api-server",
976
+ claude: "claude mcp add gxp-api mcp-serve",
977
+ codex: "codex mcp add gxp-api mcp-serve",
978
978
  gemini:
979
- "add the `gxp-api` server to `~/.gemini/settings.json` under `mcpServers` with command `gxp-api-server`",
979
+ "add the `gxp-api` server to `~/.gemini/settings.json` under `mcpServers` with command `mcp-serve`",
980
980
  }[provider] || "see the provider's MCP docs"
981
981
 
982
982
  return [
@@ -986,16 +986,18 @@ function buildInteractiveInitialPrompt(projectName, description, provider) {
986
986
  "",
987
987
  `Start a new GxP plugin development session. First read \`${instructionFile}\` and \`app-instructions.md\` in this project — they describe the workflow, conventions, and the full set of tools available to you.${claudeAgentHint}`,
988
988
  "",
989
- "**Step 0 — smoke-test the `gxp-api` MCP server before asking me anything.** The server is defined in `.mcp.json` / `.gemini/settings.json` at the project root and provided by the `gxp-api-server` binary that ships with `@gxp-dev/tools` (already on your PATH — verify with `which gxp-api-server`). Call `api_list_tags` immediately:",
989
+ "**Step 0 — smoke-test the `gxp-api` MCP server before asking me anything.** The server is defined in `.mcp.json` / `.gemini/settings.json` at the project root and provided by the `mcp-serve` binary that ships with `@gxp-dev/tools` (already on your PATH — verify with `which mcp-serve`). Call `api_list_tags` immediately:",
990
990
  "- If it returns a list of tags → the MCP is live; load the tag list into memory so your first question to me can be grounded in the platform's actual resources (attendees, events, quizzes, posts, forms, etc.) rather than abstract categories.",
991
991
  `- If \`api_list_tags\` (or any other \`api_*\` / \`config_*\` / \`docs_*\` tool) is not available, the MCP didn't register. Tell me so and suggest I run \`${mcpFixCommand}\`, then restart this session. Do not proceed without the MCP — every operationId, event, and dependency you plan with must come from it.`,
992
992
  "",
993
- "You have the `gxp-api` MCP server available with 29 tools across five families:",
993
+ "You have the `gxp-api` MCP server available with 33 tools across seven families:",
994
994
  "- **API spec discovery** — `search_api_endpoints`, `api_list_operation_ids`, `api_get_operation_parameters`, `api_find_endpoints_by_schema`, `api_generate_dependency`, `get_endpoint_details`, `api_list_tags`.",
995
995
  "- **WebSocket events** — `api_find_events_for_operation` (maps an operationId to the AsyncAPI events it triggers), `api_list_events`, `search_websocket_events`.",
996
996
  "- **Config editing** — `config_add_card`, `config_add_field`, `config_list_field_types`, `config_get_field_schema`, `config_extract_strings`, `config_validate`, etc. Every mutation is linter-guarded against the schemas in `bin/lib/lint/schemas/`.",
997
997
  "- **Docs search** — `docs_search`, `docs_get_page`, `docs_list_pages` (full-text search across docs.gxp.dev).",
998
998
  "- **Test helpers** — `test_scaffold_component_test`, `test_api_route`.",
999
+ "- **Data models** — `describe_data_models` (enumerate or detail OpenAPI components.schemas; walks allOf and resolves $ref by name).",
1000
+ "- **UIKit** — `list_uikit_components` (list components exported by `@gxp-dev/uikit` installed in this project).",
999
1001
  "",
1000
1002
  "Follow the full workflow from the instructions: (1) understand the feature, (2) discover data sources via MCP, (3) plan including the admin configuration form, (4) implement, (5) **sync the manifest and build the admin form**, (6) test with real broadcasts, (7) final `gxdev lint --all`.",
1001
1003
  "",
@@ -1033,8 +1035,8 @@ function buildInteractiveInitialPrompt(projectName, description, provider) {
1033
1035
  */
1034
1036
  function registerMcpWithProviderCli(provider) {
1035
1037
  const commands = {
1036
- claude: ["mcp", "add", "gxp-api", "gxp-api-server"],
1037
- codex: ["mcp", "add", "gxp-api", "gxp-api-server"],
1038
+ claude: ["mcp", "add", "gxp-api", "mcp-serve"],
1039
+ codex: ["mcp", "add", "gxp-api", "mcp-serve"],
1038
1040
  }
1039
1041
  const args = commands[provider]
1040
1042
  if (!args) {
@@ -1117,7 +1119,7 @@ function launchInteractiveAISession(
1117
1119
  " The `gxp-api` MCP server is configured in .mcp.json / .gemini/settings.json",
1118
1120
  )
1119
1121
  console.log(
1120
- " at the project root (binary: gxp-api-server, installed with @gxp-dev/tools).",
1122
+ " at the project root (binary: mcp-serve, installed with @gxp-dev/tools).",
1121
1123
  )
1122
1124
  console.log(
1123
1125
  " The agent will smoke-test it first, then greet you and ask what you want",
@@ -1,484 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * GxP API Specs MCP Server
4
+ * Deprecated bin. Forwards to the same stdio MCP server as `mcp-serve`,
5
+ * but prints a one-time stderr notice on startup so existing scaffolded
6
+ * projects keep working while users migrate their MCP configs to the new
7
+ * name. This shim will be removed in a future major release.
5
8
  *
6
- * Provides API documentation (OpenAPI, AsyncAPI, Webhooks) to AI coding assistants
7
- * via the Model Context Protocol (MCP).
9
+ * Migration:
10
+ * - claude: `claude mcp add gxp-api mcp-serve`
11
+ * - codex: `codex mcp add gxp-api mcp-serve`
12
+ * - gemini: set "command": "mcp-serve" in ~/.gemini/settings.json
13
+ * - .mcp.json / .gemini/settings.json: swap "gxp-api-server" -> "mcp-serve"
8
14
  *
9
- * Features:
10
- * - Fetches specs based on VITE_API_ENV environment variable
11
- * - Caches specs in memory for performance
12
- * - Provides tools for fetching full specs or searching endpoints
13
- *
14
- * Usage:
15
- * node mcp/gxp-api-server.js
16
- *
17
- * Configure in your AI tool's MCP settings to enable API-aware assistance.
18
- */
19
-
20
- const readline = require("readline")
21
- const {
22
- ENVIRONMENT_URLS,
23
- getEnvironment,
24
- getEnvUrls,
25
- fetchSpec,
26
- } = require("./lib/specs")
27
-
28
- /**
29
- * Search OpenAPI spec for endpoints matching a query
30
- */
31
- function searchEndpoints(spec, query) {
32
- const results = []
33
- const queryLower = query.toLowerCase()
34
-
35
- if (spec.paths) {
36
- for (const [path, methods] of Object.entries(spec.paths)) {
37
- for (const [method, details] of Object.entries(methods)) {
38
- if (
39
- typeof details === "object" &&
40
- (path.toLowerCase().includes(queryLower) ||
41
- details.summary?.toLowerCase().includes(queryLower) ||
42
- details.description?.toLowerCase().includes(queryLower) ||
43
- details.operationId?.toLowerCase().includes(queryLower) ||
44
- details.tags?.some((t) => t.toLowerCase().includes(queryLower)))
45
- ) {
46
- results.push({
47
- path,
48
- method: method.toUpperCase(),
49
- summary: details.summary || "",
50
- description: details.description || "",
51
- operationId: details.operationId || "",
52
- tags: details.tags || [],
53
- parameters: details.parameters || [],
54
- requestBody: details.requestBody || null,
55
- responses: Object.keys(details.responses || {}),
56
- })
57
- }
58
- }
59
- }
60
- }
61
-
62
- return results
63
- }
64
-
65
- /**
66
- * Search AsyncAPI spec for channels/events matching a query.
67
- *
68
- * Matches across:
69
- * - components.messages (event name, summary, description, x-triggered-by)
70
- * - channels (channel name, description)
71
- *
72
- * For messages, the returned `eventName` is what you pass to
73
- * store.listen(eventName, permissionIdentifier, callback) on the client.
74
- */
75
- function searchEvents(spec, query) {
76
- const results = []
77
- const queryLower = query.toLowerCase()
78
-
79
- const messages = spec?.components?.messages || {}
80
- for (const [eventName, message] of Object.entries(messages)) {
81
- if (typeof message !== "object" || message === null) continue
82
- const trigger = message["x-triggered-by"] || ""
83
- if (
84
- eventName.toLowerCase().includes(queryLower) ||
85
- message.summary?.toLowerCase().includes(queryLower) ||
86
- message.description?.toLowerCase().includes(queryLower) ||
87
- trigger.toLowerCase().includes(queryLower)
88
- ) {
89
- results.push({
90
- kind: "event",
91
- eventName,
92
- summary: message.summary || "",
93
- description: message.description || "",
94
- triggeredBy: trigger || null,
95
- payloadRef: message.payload?.$ref || null,
96
- })
97
- }
98
- }
99
-
100
- if (spec.channels) {
101
- for (const [channel, details] of Object.entries(spec.channels)) {
102
- if (
103
- channel.toLowerCase().includes(queryLower) ||
104
- details.description?.toLowerCase().includes(queryLower)
105
- ) {
106
- const operations = []
107
- if (details.publish) {
108
- operations.push({
109
- type: "publish",
110
- summary: details.publish.summary || "",
111
- message: details.publish.message || null,
112
- })
113
- }
114
- if (details.subscribe) {
115
- operations.push({
116
- type: "subscribe",
117
- summary: details.subscribe.summary || "",
118
- message: details.subscribe.message || null,
119
- })
120
- }
121
-
122
- results.push({
123
- kind: "channel",
124
- channel,
125
- description: details.description || "",
126
- operations,
127
- })
128
- }
129
- }
130
- }
131
-
132
- return results
133
- }
134
-
135
- /**
136
- * Get endpoint details by path and method
137
- */
138
- function getEndpointDetails(spec, path, method) {
139
- const methodLower = method.toLowerCase()
140
- const endpoint = spec.paths?.[path]?.[methodLower]
141
-
142
- if (!endpoint) {
143
- return null
144
- }
145
-
146
- return {
147
- path,
148
- method: method.toUpperCase(),
149
- summary: endpoint.summary || "",
150
- description: endpoint.description || "",
151
- operationId: endpoint.operationId || "",
152
- tags: endpoint.tags || [],
153
- parameters: endpoint.parameters || [],
154
- requestBody: endpoint.requestBody || null,
155
- responses: endpoint.responses || {},
156
- security: endpoint.security || spec.security || [],
157
- }
158
- }
159
-
160
- // MCP Server Implementation
161
- const {
162
- CONFIG_TOOLS,
163
- handleConfigToolCall,
164
- isConfigTool,
165
- } = require("./lib/config-tools")
166
-
167
- const {
168
- EXT_API_TOOLS,
169
- handleExtApiToolCall,
170
- isExtApiTool,
171
- } = require("./lib/api-tools")
172
-
173
- const {
174
- DOCS_TOOLS,
175
- handleDocsToolCall,
176
- isDocsTool,
177
- } = require("./lib/docs-tools")
178
-
179
- const {
180
- TEST_TOOLS,
181
- handleTestToolCall,
182
- isTestTool,
183
- } = require("./lib/test-tools")
184
-
185
- const SERVER_INFO = {
186
- name: "gxp-api-server",
187
- version: "2.0.0",
188
- description:
189
- "GxP toolkit MCP server: API specs, config/manifest editing, documentation search, and plugin test helpers for AI coding assistants.",
190
- }
191
-
192
- const API_TOOLS = [
193
- {
194
- name: "get_openapi_spec",
195
- description:
196
- "Fetch the full OpenAPI specification for the GxP API. Returns the complete spec including all endpoints, schemas, and documentation.",
197
- inputSchema: {
198
- type: "object",
199
- properties: {},
200
- required: [],
201
- },
202
- },
203
- {
204
- name: "get_asyncapi_spec",
205
- description:
206
- "Fetch the AsyncAPI specification for GxP WebSocket events. Returns channel definitions, message schemas, and event documentation.",
207
- inputSchema: {
208
- type: "object",
209
- properties: {},
210
- required: [],
211
- },
212
- },
213
- {
214
- name: "search_api_endpoints",
215
- description:
216
- "Search for API endpoints matching a query. Searches path, summary, description, operation ID, and tags.",
217
- inputSchema: {
218
- type: "object",
219
- properties: {
220
- query: {
221
- type: "string",
222
- description:
223
- "Search term to find matching endpoints (e.g., 'attendee', 'check-in', 'event')",
224
- },
225
- },
226
- required: ["query"],
227
- },
228
- },
229
- {
230
- name: "search_websocket_events",
231
- description:
232
- "Search AsyncAPI events matching a query. Searches components.messages (event name, summary, description, x-triggered-by) and channel definitions. The returned eventName is what you pass to store.listen(eventName, permissionIdentifier, callback).",
233
- inputSchema: {
234
- type: "object",
235
- properties: {
236
- query: {
237
- type: "string",
238
- description:
239
- "Search term to find matching events (e.g., 'message', 'created', 'updated')",
240
- },
241
- },
242
- required: ["query"],
243
- },
244
- },
245
- {
246
- name: "get_endpoint_details",
247
- description:
248
- "Get detailed information about a specific API endpoint including parameters, request body, and responses.",
249
- inputSchema: {
250
- type: "object",
251
- properties: {
252
- path: {
253
- type: "string",
254
- description: "API endpoint path (e.g., '/api/v1/attendees')",
255
- },
256
- method: {
257
- type: "string",
258
- description: "HTTP method (GET, POST, PUT, PATCH, DELETE)",
259
- },
260
- },
261
- required: ["path", "method"],
262
- },
263
- },
264
- {
265
- name: "get_api_environment",
266
- description:
267
- "Get the current API environment configuration including base URL and spec URLs.",
268
- inputSchema: {
269
- type: "object",
270
- properties: {},
271
- required: [],
272
- },
273
- },
274
- ]
275
-
276
- // Final tool set surfaced to MCP clients: API spec tools + extended API tools
277
- // + config-editor tools + doc-search tools + test tools.
278
- const TOOLS = [
279
- ...API_TOOLS,
280
- ...EXT_API_TOOLS,
281
- ...CONFIG_TOOLS,
282
- ...DOCS_TOOLS,
283
- ...TEST_TOOLS,
284
- ]
285
-
286
- /**
287
- * Handle MCP tool calls
288
- */
289
- async function handleToolCall(name, args) {
290
- if (isConfigTool(name)) {
291
- return handleConfigToolCall(name, args)
292
- }
293
- if (isExtApiTool(name)) {
294
- return handleExtApiToolCall(name, args)
295
- }
296
- if (isDocsTool(name)) {
297
- return handleDocsToolCall(name, args)
298
- }
299
- if (isTestTool(name)) {
300
- return handleTestToolCall(name, args)
301
- }
302
- switch (name) {
303
- case "get_openapi_spec": {
304
- const spec = await fetchSpec("openapi")
305
- return {
306
- content: [
307
- {
308
- type: "text",
309
- text: JSON.stringify(spec, null, 2),
310
- },
311
- ],
312
- }
313
- }
314
-
315
- case "get_asyncapi_spec": {
316
- const spec = await fetchSpec("asyncapi")
317
- return {
318
- content: [
319
- {
320
- type: "text",
321
- text: JSON.stringify(spec, null, 2),
322
- },
323
- ],
324
- }
325
- }
326
-
327
- case "search_api_endpoints": {
328
- const spec = await fetchSpec("openapi")
329
- const results = searchEndpoints(spec, args.query)
330
- return {
331
- content: [
332
- {
333
- type: "text",
334
- text:
335
- results.length > 0
336
- ? JSON.stringify(results, null, 2)
337
- : `No endpoints found matching "${args.query}"`,
338
- },
339
- ],
340
- }
341
- }
342
-
343
- case "search_websocket_events": {
344
- const spec = await fetchSpec("asyncapi")
345
- const results = searchEvents(spec, args.query)
346
- return {
347
- content: [
348
- {
349
- type: "text",
350
- text:
351
- results.length > 0
352
- ? JSON.stringify(results, null, 2)
353
- : `No events found matching "${args.query}"`,
354
- },
355
- ],
356
- }
357
- }
358
-
359
- case "get_endpoint_details": {
360
- const spec = await fetchSpec("openapi")
361
- const details = getEndpointDetails(spec, args.path, args.method)
362
- return {
363
- content: [
364
- {
365
- type: "text",
366
- text: details
367
- ? JSON.stringify(details, null, 2)
368
- : `Endpoint not found: ${args.method} ${args.path}`,
369
- },
370
- ],
371
- }
372
- }
373
-
374
- case "get_api_environment": {
375
- const env = getEnvironment()
376
- const urls = getEnvUrls()
377
- return {
378
- content: [
379
- {
380
- type: "text",
381
- text: JSON.stringify(
382
- {
383
- environment: env,
384
- ...urls,
385
- },
386
- null,
387
- 2,
388
- ),
389
- },
390
- ],
391
- }
392
- }
393
-
394
- default:
395
- throw new Error(`Unknown tool: ${name}`)
396
- }
397
- }
398
-
399
- /**
400
- * Process MCP JSON-RPC request
15
+ * The notice is written to stderr (not stdout) so it doesn't corrupt the
16
+ * MCP JSON-RPC stream the client is reading.
401
17
  */
402
- async function processRequest(request) {
403
- const { method, params, id } = request
404
-
405
- try {
406
- let result
407
-
408
- switch (method) {
409
- case "initialize":
410
- result = {
411
- protocolVersion: "2024-11-05",
412
- capabilities: {
413
- tools: {},
414
- },
415
- serverInfo: SERVER_INFO,
416
- }
417
- break
418
-
419
- case "tools/list":
420
- result = { tools: TOOLS }
421
- break
422
-
423
- case "tools/call":
424
- result = await handleToolCall(params.name, params.arguments || {})
425
- break
426
-
427
- case "notifications/initialized":
428
- // No response needed for notifications
429
- return null
430
-
431
- default:
432
- throw new Error(`Unknown method: ${method}`)
433
- }
434
-
435
- return { jsonrpc: "2.0", id, result }
436
- } catch (error) {
437
- return {
438
- jsonrpc: "2.0",
439
- id,
440
- error: {
441
- code: -32603,
442
- message: error.message,
443
- },
444
- }
445
- }
446
- }
447
-
448
- /**
449
- * Main server loop
450
- */
451
- async function main() {
452
- const rl = readline.createInterface({
453
- input: process.stdin,
454
- output: process.stdout,
455
- terminal: false,
456
- })
457
-
458
- for await (const line of rl) {
459
- if (!line.trim()) continue
460
18
 
461
- try {
462
- const request = JSON.parse(line)
463
- const response = await processRequest(request)
19
+ process.stderr.write(
20
+ "[gxp-api-server] DEPRECATED: this bin name will be removed in a future major release. Use `mcp-serve` instead — update your .mcp.json / .gemini/settings.json or rerun `claude mcp add gxp-api mcp-serve`.\n",
21
+ )
464
22
 
465
- if (response) {
466
- console.log(JSON.stringify(response))
467
- }
468
- } catch (error) {
469
- console.log(
470
- JSON.stringify({
471
- jsonrpc: "2.0",
472
- id: null,
473
- error: {
474
- code: -32700,
475
- message: `Parse error: ${error.message}`,
476
- },
477
- }),
478
- )
479
- }
480
- }
481
- }
23
+ const { startServer } = require("./lib/server")
482
24
 
483
- // Run server
484
- main().catch(console.error)
25
+ startServer().catch((err) => {
26
+ console.error(err && err.stack ? err.stack : err)
27
+ process.exit(1)
28
+ })