@stellisoft/stellify-mcp 0.1.26 → 0.1.28

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
@@ -26,7 +26,7 @@ const STELLIFY_FRAMEWORK_API = {
26
26
  List: ['create', 'from', 'range', 'add', 'remove', 'removeWhere', 'set', 'get', 'first', 'last', 'sort', 'sortBy', 'reverse', 'filter', 'find', 'findIndex', 'map', 'reduce', 'forEach', 'includes', 'indexOf', 'every', 'some', 'slice', 'take', 'skip', 'chunk', 'unique', 'uniqueBy', 'groupBy', 'flatten', 'concat', 'isEmpty', 'isNotEmpty', 'count', 'clear', 'toArray', 'toJSON', 'clone', 'sum', 'avg', 'min', 'max'],
27
27
  Tree: ['create', 'setRoot', 'addChild', 'removeNode', 'getNode', 'getRoot', 'getChildren', 'getParent', 'getSiblings', 'getAncestors', 'getDescendants', 'getDepth', 'getPath', 'traverse', 'find', 'findAll', 'move', 'toArray', 'size'],
28
28
  // Network
29
- Http: ['create', 'get', 'post', 'put', 'patch', 'delete', 'withHeaders', 'withToken', 'withTimeout'],
29
+ Http: ['create', 'get', 'post', 'put', 'patch', 'delete', 'items', 'withHeaders', 'withToken', 'withTimeout'],
30
30
  Socket: ['create', 'connect', 'disconnect', 'send', 'sendEvent', 'on', 'off', 'once', 'isConnected', 'getState'],
31
31
  Auth: ['create', 'login', 'logout', 'fetchUser', 'getUser', 'getToken', 'isAuthenticated', 'setToken', 'setUser', 'refresh', 'onAuthChange', 'offAuthChange', 'getAuthHeader'],
32
32
  Stream: ['create', 'headers', 'withToken', 'onChunk', 'onComplete', 'onError', 'get', 'post', 'abort', 'getBuffer', 'getChunks', 'isStreaming', 'clear'],
@@ -77,12 +77,8 @@ The npm package is "stellify-framework" (NOT @stellify/core).
77
77
  Import like: import { Http, List, Form } from 'stellify-framework';
78
78
 
79
79
  IMPORTANT - List class and Vue reactivity:
80
- The List class methods return List instances, NOT plain arrays.
81
- Vue's v-for directive cannot iterate over List instances directly.
82
-
83
- When assigning to a Vue ref that will be used with v-for, call .toArray():
84
- - CORRECT: notes.value = List.from(response.data).toArray();
85
- - INCORRECT (v-for won't work): notes.value = List.from(response.data);
80
+ List is iterable and works directly with Vue's v-for directive.
81
+ Use List.from() to wrap arrays for chainable operations (filter, map, sort, etc.).
86
82
 
87
83
  Example response:
88
84
  {
@@ -138,13 +134,16 @@ LEGACY (still supported but prefer combined tools above):
138
134
 
139
135
  For PHP: Use type='class', 'model', 'controller', or 'middleware'.
140
136
  For Vue: Use type='js' and extension='vue'. Place in the 'js' directory.
137
+ - Auto-creates app.js (check response.appJs)
138
+ - Auto-creates template route for visual editor (check response.templateRoute.uuid)
141
139
 
142
140
  DEPENDENCY RESOLUTION (automatic):
143
- Pass 'includes' as an array of namespace strings (e.g., ["App\\Models\\User", "Illuminate\\Support\\Facades\\Hash"]).
144
- The system resolves these to UUIDs automatically, creating missing dependencies on-demand:
145
- - App\\* classes → creates stub file in your project (tenant DB)
141
+ Pass 'includes' as an array of namespace strings for FRAMEWORK classes (e.g., ["Illuminate\\Http\\Request", "Illuminate\\Support\\Facades\\Hash"]).
142
+ The system resolves these to UUIDs automatically:
146
143
  - Illuminate\\*/Laravel\\* → fetches from Laravel API, creates in Application DB
147
- - Vendor packages → fetches from vendor, creates in Application DB`,
144
+ - Vendor packages → fetches from vendor, creates in Application DB
145
+
146
+ NOTE: For controllers that use PROJECT models (Feedback, Vote, etc.), add those to the 'models' array in save_file instead. Do NOT put project models in includes - this causes duplicate use statement errors.`,
148
147
  inputSchema: {
149
148
  type: 'object',
150
149
  properties: {
@@ -186,7 +185,9 @@ The system resolves these to UUIDs automatically, creating missing dependencies
186
185
  name: 'create_method',
187
186
  description: `Create a method in a file. Can optionally include the body and async flag in a single call.
188
187
 
189
- **NEW: Combined creation** - You can now pass 'body' and 'is_async' to create the complete method in ONE call instead of three (create_method add_method_body → save_method).
188
+ **NEW: Combined creation** - Pass 'body' to create the complete method in ONE call. Async is auto-detected when body contains \`await\`.
189
+
190
+ **Nested code is handled correctly.** The parser tracks brace/bracket/paren depth and only splits statements on semicolons at the top level. This means computed properties, arrow functions with block bodies, and other nested constructs work correctly as single statements.
190
191
 
191
192
  Parameters are automatically created as clauses. The response includes the clause UUIDs for each parameter.
192
193
 
@@ -202,12 +203,11 @@ Example request (simple - signature only):
202
203
  ]
203
204
  }
204
205
 
205
- Example request (combined - with body and async):
206
+ Example request (combined - with body, async auto-detected):
206
207
  {
207
208
  "file": "file-uuid",
208
209
  "name": "fetchData",
209
- "body": "const response = await Http.get('/api/data');\\nreturn response.data;",
210
- "is_async": true
210
+ "body": "const response = await Http.get('/api/data');\\nreturn response.data;"
211
211
  }
212
212
 
213
213
  Example response includes:
@@ -289,6 +289,8 @@ Example response includes:
289
289
  name: 'add_method_body',
290
290
  description: `Parse and add PHP code to a method body. Provide the method implementation code (without the function declaration). Stellify will parse it into structured statements.
291
291
 
292
+ **Nested code is handled correctly.** The parser tracks brace/bracket/paren depth and only splits on semicolons at the top level. Arrow functions with block bodies, computed properties, and other nested constructs work as single statements.
293
+
292
294
  IMPORTANT: This APPENDS to existing method statements. To REPLACE a method's code:
293
295
  1. Create a NEW method with create_method
294
296
  2. Add body with add_method_body
@@ -456,7 +458,9 @@ Example - Remove duplicate/unwanted statements:
456
458
  },
457
459
  {
458
460
  name: 'create_route',
459
- description: `Create a route/page. For API routes, pass controller and controller_method UUIDs to wire execution.
461
+ description: `Create a route/page. For API routes, you MUST pass BOTH controller AND controller_method UUIDs to wire execution.
462
+
463
+ IMPORTANT: Both 'controller' (file UUID) and 'controller_method' (method UUID) are required together for API routes to execute code. Without both, the route won't run any code.
460
464
 
461
465
  Route params like {id} auto-inject into controller method parameters when names match.`,
462
466
  inputSchema: {
@@ -488,11 +492,11 @@ Route params like {id} auto-inject into controller method parameters when names
488
492
  },
489
493
  controller: {
490
494
  type: 'string',
491
- description: 'UUID of the controller file to handle this route. Required for API routes to execute code.',
495
+ description: 'UUID of the controller file. MUST be provided together with controller_method for API routes to execute code.',
492
496
  },
493
497
  controller_method: {
494
498
  type: 'string',
495
- description: 'UUID of the method within the controller to execute. Required for API routes to execute code.',
499
+ description: 'UUID of the method to execute. MUST be provided together with controller for API routes to execute code.',
496
500
  },
497
501
  data: {
498
502
  type: 'object',
@@ -527,6 +531,7 @@ Use this to look up a route you created or to find existing routes in the projec
527
531
  description: `Update an existing route/page. Use this to wire a route to a controller method.
528
532
 
529
533
  IMPORTANT: This is how you connect API routes to controller methods!
534
+ IMPORTANT: You MUST provide BOTH controller AND controller_method together - they are a pair.
530
535
 
531
536
  Example - Wire an API route to a controller method:
532
537
  {
@@ -536,8 +541,8 @@ Example - Wire an API route to a controller method:
536
541
  }
537
542
 
538
543
  Available fields:
539
- - controller: UUID of the controller file
540
- - controller_method: UUID of the method to execute
544
+ - controller: UUID of the controller file (MUST be paired with controller_method)
545
+ - controller_method: UUID of the method to execute (MUST be paired with controller)
541
546
  - path: URL path
542
547
  - name: Route name
543
548
  - type: "web" or "api"
@@ -553,11 +558,11 @@ Available fields:
553
558
  },
554
559
  controller: {
555
560
  type: 'string',
556
- description: 'UUID of the controller file to handle this route',
561
+ description: 'UUID of the controller file. MUST be provided together with controller_method.',
557
562
  },
558
563
  controller_method: {
559
564
  type: 'string',
560
- description: 'UUID of the method within the controller to execute',
565
+ description: 'UUID of the method to execute. MUST be provided together with controller.',
561
566
  },
562
567
  path: {
563
568
  type: 'string',
@@ -679,11 +684,22 @@ Generates: <div class="card p-4" v-for="note in notes" :key="note.id">`,
679
684
  name: 'update_element',
680
685
  description: `Update a UI element's attributes.
681
686
 
682
- Pass data object with: tag, classes (array), text, variable (for v-model), and event handlers (click, submit, etc. = method UUID).
687
+ Pass data object with: tag, classes (array), text, variable (for v-model), and event handlers.
683
688
 
684
689
  Key fields: inputType (not 'type') for button/input HTML type. clickArgs for handler arguments in v-for loops.
685
690
 
686
- Event handlers: click, submit, change, input, focus, blur, keydown, keyup, mouseenter, mouseleave.`,
691
+ EVENT HANDLERS - Use method UUIDs:
692
+ { "click": "method-uuid" } → @click="methodName"
693
+ { "click": "method-uuid", "clickArgs": "item" } → @click="methodName(item)"
694
+
695
+ Create methods for all handlers, including simple state changes like opening modals or toggling flags.
696
+
697
+ Event types: click, submit, change, input, focus, blur, keydown, keyup, mouseenter, mouseleave.
698
+
699
+ EFFICIENCY - Prefer updates over delete/recreate:
700
+ - Move between routes: change \`routeParent\` attribute
701
+ - Reparent elements: change \`parent\` attribute
702
+ - Reorder children: update parent's \`data\` array with new UUID order`,
687
703
  inputSchema: {
688
704
  type: 'object',
689
705
  properties: {
@@ -776,6 +792,8 @@ Note: To reorder elements, use update_element to modify the parent element's 'da
776
792
 
777
793
  Auto-detects Vue bindings ({{ var }}). For Vue components, omit 'page'. For pages, provide route UUID.
778
794
 
795
+ **@click auto-wiring:** Pass 'file' UUID to auto-resolve @click="methodName" handlers. Methods must exist in the file first.
796
+
779
797
  Prefer SVG icons over emoji (encoding issues).`,
780
798
  inputSchema: {
781
799
  type: 'object',
@@ -792,6 +810,10 @@ Prefer SVG icons over emoji (encoding issues).`,
792
810
  type: 'string',
793
811
  description: 'Parent element UUID to attach to (alternative to page)',
794
812
  },
813
+ file: {
814
+ type: 'string',
815
+ description: 'Vue component file UUID. Pass this to auto-wire @click handlers to method UUIDs.',
816
+ },
795
817
  test: {
796
818
  type: 'boolean',
797
819
  description: 'If true, returns structure without creating elements',
@@ -852,11 +874,13 @@ For Vue components, include the returned statement UUID in save_file's 'statemen
852
874
 
853
875
  **PREFERRED:** Use this instead of the two-step create_statement → add_statement_code process.
854
876
 
877
+ **Nested code is handled correctly.** The parser tracks brace/bracket/paren depth and only splits on top-level semicolons. Computed properties, arrow functions with block bodies, and other nested constructs are kept as single statements.
878
+
855
879
  Examples:
856
880
  - PHP: "use Illuminate\\Http\\Request;" or "private $items = [];"
857
881
  - JS/Vue: "const count = ref(0);" or "import { ref } from 'vue';"
858
882
 
859
- For Vue components, include the returned statement UUID in save_file's 'statements' array (NOT 'data' - that's for methods).`,
883
+ For Vue components, include the returned statement UUIDs in save_file's 'statements' array (NOT 'data' - that's for methods).`,
860
884
  inputSchema: {
861
885
  type: 'object',
862
886
  properties: {
@@ -880,7 +904,7 @@ For Vue components, include the returned statement UUID in save_file's 'statemen
880
904
  name: 'add_statement_code',
881
905
  description: `Add code to an existing statement. This is step 2 of 2 - call this AFTER create_statement.
882
906
 
883
- **ALTERNATIVE:** Use create_statement_with_code for a single-call approach.
907
+ **ALTERNATIVE:** Use create_statement_with_code for a single-call approach that combines both steps.
884
908
 
885
909
  The statement must already exist (created via create_statement). This parses and stores the code.
886
910
 
@@ -1018,12 +1042,12 @@ This works the same as create_file's dependency resolution.`,
1018
1042
  includes: {
1019
1043
  type: 'array',
1020
1044
  items: { type: 'string' },
1021
- description: 'Array of file UUIDs OR namespace strings to import. Namespace strings (e.g., "Illuminate\\\\Http\\\\JsonResponse") are auto-resolved to UUIDs.',
1045
+ description: 'Array of file UUIDs OR namespace strings for FRAMEWORK classes only (Request, JsonResponse, etc.). Do NOT put project models here - use the models array instead.',
1022
1046
  },
1023
1047
  models: {
1024
1048
  type: 'array',
1025
1049
  items: { type: 'string' },
1026
- description: 'Array of model file UUIDs that this controller uses (required for model class loading)',
1050
+ description: 'Array of model file UUIDs for PROJECT models (Feedback, Vote, etc.). These get sandbox namespace automatically. Do NOT also add these to includes or you will get duplicate use statement errors.',
1027
1051
  },
1028
1052
  },
1029
1053
  required: ['uuid', 'name', 'type'],
@@ -1458,6 +1482,111 @@ The response includes actionable suggestions like:
1458
1482
  },
1459
1483
  },
1460
1484
  },
1485
+ {
1486
+ name: 'get_setting',
1487
+ description: `Get a setting/config value from the tenant's settings table.
1488
+
1489
+ These settings are read by the config() function in sandbox code execution.
1490
+ Use this to check existing configuration values before modifying them.
1491
+
1492
+ EXAMPLE:
1493
+ { "name": "app" }
1494
+
1495
+ Returns the setting data as key-value pairs, e.g.:
1496
+ {
1497
+ "name": "My App",
1498
+ "timezone": "UTC",
1499
+ "locale": "en"
1500
+ }
1501
+
1502
+ Common setting profiles:
1503
+ - "app": Application settings (name, timezone, locale)
1504
+ - "database": Database connection settings
1505
+ - "mail": Mail configuration
1506
+ - "cache": Cache settings
1507
+ - Custom profiles for app-specific config`,
1508
+ inputSchema: {
1509
+ type: 'object',
1510
+ properties: {
1511
+ name: {
1512
+ type: 'string',
1513
+ description: 'Setting profile name (e.g., "app", "database", "mail", or custom names like "vote")',
1514
+ },
1515
+ },
1516
+ required: ['name'],
1517
+ },
1518
+ },
1519
+ {
1520
+ name: 'save_setting',
1521
+ description: `Create or update a setting in the tenant's settings table.
1522
+
1523
+ These settings are accessible via config() in sandbox code execution.
1524
+ Use this to configure application behavior, API keys, feature flags, etc.
1525
+
1526
+ IMPORTANT: This creates or updates the setting profile with the provided key-value data.
1527
+ The data is merged with any existing values for that profile.
1528
+
1529
+ EXAMPLE - Create app settings:
1530
+ {
1531
+ "name": "app",
1532
+ "data": {
1533
+ "name": "My Feedback App",
1534
+ "timezone": "America/New_York",
1535
+ "locale": "en"
1536
+ }
1537
+ }
1538
+
1539
+ EXAMPLE - Create custom settings for voting:
1540
+ {
1541
+ "name": "vote",
1542
+ "data": {
1543
+ "salt": "my-secret-salt-for-ip-hashing",
1544
+ "allow_anonymous": true,
1545
+ "max_votes_per_day": 10
1546
+ }
1547
+ }
1548
+
1549
+ In your controller code, access these with:
1550
+ - config('app.name') returns "My Feedback App"
1551
+ - config('vote.salt') returns "my-secret-salt-for-ip-hashing"
1552
+ - config('vote.allow_anonymous') returns true`,
1553
+ inputSchema: {
1554
+ type: 'object',
1555
+ properties: {
1556
+ name: {
1557
+ type: 'string',
1558
+ description: 'Setting profile name (e.g., "app", "vote", "features")',
1559
+ },
1560
+ data: {
1561
+ type: 'object',
1562
+ description: 'Key-value pairs for the setting (e.g., { "salt": "secret", "enabled": true })',
1563
+ },
1564
+ },
1565
+ required: ['name', 'data'],
1566
+ },
1567
+ },
1568
+ {
1569
+ name: 'delete_setting',
1570
+ description: `Delete a setting profile from the tenant's settings table.
1571
+
1572
+ WARNING: This permanently removes the entire setting profile and all its values.
1573
+ This cannot be undone.
1574
+
1575
+ EXAMPLE:
1576
+ { "name": "vote" }
1577
+
1578
+ This removes the "vote" setting profile entirely.`,
1579
+ inputSchema: {
1580
+ type: 'object',
1581
+ properties: {
1582
+ name: {
1583
+ type: 'string',
1584
+ description: 'Setting profile name to delete',
1585
+ },
1586
+ },
1587
+ required: ['name'],
1588
+ },
1589
+ },
1461
1590
  ];
1462
1591
  // Server instructions for tool discovery (used by MCP Tool Search)
1463
1592
  const SERVER_INSTRUCTIONS = `Stellify is a coding platform where you code alongside AI on a codebase maintained and curated by AI. Build Laravel, stellify-framework, and Vue.js applications.
@@ -1486,114 +1615,76 @@ Key concepts:
1486
1615
 
1487
1616
  1. get_project → find 'js' directory UUID (or create it)
1488
1617
  2. create_file → type='js', extension='vue' for the component
1489
- 3. **Create a template route** for editor access:
1490
- - create_route with name like "notes-template" or "component-name-template"
1491
- - This route is NOT where the component displays - it's only for accessing the template in the Stellify visual editor
1492
- - The actual display route (e.g., "/notes") will have just \`<div id="app"></div>\` where Vue mounts
1493
- 4. Create statements for imports using **create_statement_with_code** (ONE call each):
1618
+ - **Auto-creates app.js** and **template route** (check response for appJs and templateRoute fields)
1619
+ 3. Create statements for imports using **create_statement_with_code** (ONE call each):
1494
1620
  - create_statement_with_code(file, "import { ref, onMounted } from 'vue';")
1495
1621
  - create_statement_with_code(file, "import { Http } from 'stellify-framework';")
1496
- NOTE: The npm package is "stellify-framework" (NOT @stellify/core)
1497
- 5. Create statements for reactive data: create_statement_with_code(file, "const notes = ref([]);")
1498
- 6. **create_method with body and is_async** (ONE call instead of three). Example:
1622
+ 4. Create statements for reactive data: create_statement_with_code(file, "const notes = ref([]);")
1623
+ 5. **create_method with body** (async auto-detected from \`await\`). Example:
1499
1624
  \`\`\`
1500
1625
  create_method({
1501
1626
  file: fileUuid,
1502
1627
  name: "fetchNotes",
1503
- body: "const response = await Http.get('/api/notes');\\nnotes.value = response.data || [];",
1504
- is_async: true
1628
+ body: "const response = await Http.get('/api/notes');\\nnotes.value = response.data || [];"
1505
1629
  })
1506
1630
  \`\`\`
1507
- 7. Create statement for onMounted: create_statement_with_code(file, "onMounted(fetchNotes);")
1508
- 8. html_to_elements template **with page=templateRouteUuid** (attach to the template route for editor access)
1509
- 9. update_element → wire click handlers to method UUIDs
1510
- 10. save_file with: extension='vue', template=[elementUuid], data=[methodUuids], statements=[importUuids, refUuids, onMountedUuid]
1511
- - **data = method UUIDs only** (functions)
1512
- - **statements = statement UUIDs** (imports, refs, onMounted)
1513
- 11. **MANDATORY: Create app.js entry file** (see "CRITICAL: JavaScript Entry File" section below)
1514
- 12. Create web route for the display page (e.g., "/notes")
1515
- 13. **MANDATORY: Add a div with id="app"** to the display page using html_to_elements:
1516
- - html_to_elements with page=routeUuid and elements='<div id="app"></div>'
1517
- - This is where Vue mounts the component. Without it, nothing renders!
1518
-
1519
- ## Real-Time UI (broadcast_element_command)
1520
-
1521
- Use for SHOW/DISPLAY/DEMONSTRATE requests - sends instant WebSocket updates to browser.
1522
- Use html_to_elements/update_element for permanent/saved changes.
1523
-
1524
- ## CRITICAL: Http Response Handling (Most Common Error)
1525
-
1526
- **This is the #1 cause of "notes not displaying" bugs.**
1527
-
1528
- When fetching data from API endpoints, stellify-framework's Http class returns the JSON body DIRECTLY - it does NOT wrap responses like axios does.
1631
+ 6. Create statement for onMounted: create_statement_with_code(file, "onMounted(fetchData);")
1632
+ 7. **Create UI interaction methods** for any button that changes state:
1633
+ \`\`\`
1634
+ create_method({ file: fileUuid, name: "openModal", body: "showModal.value = true;" })
1635
+ create_method({ file: fileUuid, name: "closeModal", body: "showModal.value = false;" })
1636
+ \`\`\`
1637
+ 8. html_to_elements with @click handlers auto-wired:
1638
+ \`\`\`
1639
+ html_to_elements({
1640
+ elements: '<button @click="openModal">Open</button>',
1641
+ page: templateRoute.uuid,
1642
+ file: fileUuid // Auto-wires @click="openModal" to the method UUID
1643
+ })
1644
+ \`\`\`
1645
+ 9. save_file with: extension='vue', template=[elementUuid], data=[methodUuids], statements=[importUuids, refUuids, onMountedUuid]
1646
+ 11. Create web route for the display page (e.g., "/notes")
1647
+ 12. Add \`<div id="app"></div>\` to the display page: html_to_elements(page=routeUuid, elements='<div id="app"></div>')
1529
1648
 
1530
- Laravel pagination returns: \`{ data: [...items], current_page: 1, per_page: 15, ... }\`
1649
+ ## Fetching Paginated Data
1531
1650
 
1532
- Since Http.get() returns this JSON directly (no axios wrapper), you access the array with ONE \`.data\`:
1651
+ Use \`Http.items()\` for paginated endpoints - it auto-extracts the array from Laravel responses:
1533
1652
 
1534
1653
  \`\`\`javascript
1535
- // CORRECT - Http returns JSON directly, .data gets the paginated array
1536
- const response = await Http.get('/api/notes');
1537
- notes.value = response.data || [];
1654
+ // Recommended - auto-extracts .data from paginated response
1655
+ notes.value = await Http.items('/api/notes');
1538
1656
 
1539
- // WRONG - Double .data (axios habit) - returns undefined, notes stay empty!
1657
+ // Alternative - manual extraction
1540
1658
  const response = await Http.get('/api/notes');
1541
- notes.value = response.data.data || []; // BUG: response.data.data is undefined
1659
+ notes.value = response.data || [];
1542
1660
  \`\`\`
1543
1661
 
1544
- **Why this mistake happens:** With axios, you write \`response.data.data\` because axios wraps the HTTP response (\`response.data\` unwraps axios, then \`.data\` gets the pagination array). Stellify's Http skips the wrapper, so you only need ONE \`.data\`.
1662
+ ## Editing Existing Code
1545
1663
 
1546
- **Symptom:** Notes exist in database, API returns them, but UI shows empty list or "No notes yet" message.
1664
+ - **Edit at the right level:** To change code, edit the statement or clause - never delete an entire method just to change a line.
1547
1665
 
1548
1666
  ## Common Pitfalls
1549
1667
 
1550
- - **Vue template editor access:** Templates MUST be attached to a route for users to edit them in the visual editor. Create a separate "template route" (e.g., "notes-template") and pass its UUID to html_to_elements. This is different from the display route where the component renders.
1668
+ - **@click auto-wiring:** Pass the file UUID to html_to_elements to auto-wire @click handlers. Methods must be created BEFORE calling html_to_elements.
1551
1669
  - **Stellify imports:** Use "stellify-framework" package (NOT @stellify/core)
1552
1670
  CORRECT: import { Http, List, Form } from 'stellify-framework';
1553
1671
  WRONG: import { Http } from '@stellify/core';
1554
1672
  - v-model requires ref(), NOT Form class: const formData = ref({title: ''})
1555
- - List.from() returns List, not array - use .toArray() for v-for
1673
+ - List is iterable and works directly with v-for (no .toArray() needed)
1556
1674
  - add_method_body APPENDS, doesn't replace - create new method to replace
1557
1675
  - 'data' array = method UUIDs, 'statements' array = import/variable UUIDs
1558
1676
  - For buttons in forms, set inputType: "button" to prevent auto-submit
1559
- - **Async methods:** Methods using await MUST be marked async. Use is_async: true in create_method (preferred) or save_method
1677
+ - **Async methods:** Auto-detected when body contains \`await\`. Response includes \`is_async: true\` if detected.
1560
1678
  - **onMounted:** Use direct method reference: "onMounted(fetchNotes);"
1561
1679
  The assembler automatically outputs lifecycle hooks after method definitions.
1562
1680
 
1563
- ## CRITICAL: Nullable Refs and v-if Guards
1564
-
1565
- **This causes "Cannot read properties of null" errors.**
1566
-
1567
- When using a nullable ref (e.g., \`const editingNote = ref(null)\`) with v-model or property access in templates, you MUST guard with an explicit v-if that checks the ref is not null.
1568
-
1569
- **WRONG - v-else does NOT protect against null evaluation:**
1570
- \`\`\`html
1571
- <template v-if="!editingItem">
1572
- <!-- view mode -->
1573
- </template>
1574
- <template v-else>
1575
- <input v-model="editingItem.title" /> <!-- ERROR: editingItem could be null -->
1576
- </template>
1577
- \`\`\`
1578
-
1579
- **CORRECT - explicit v-if with null check:**
1580
- \`\`\`html
1581
- <template v-if="!editingItem || editingItem.id !== item.id">
1582
- <!-- view mode -->
1583
- </template>
1584
- <template v-if="editingItem && editingItem.id === item.id">
1585
- <input v-model="editingItem.title" /> <!-- Safe: editingItem is guaranteed non-null -->
1586
- </template>
1587
- \`\`\`
1681
+ ## Nullable Refs: Use explicit v-if, NOT v-else
1588
1682
 
1589
- **Why v-else fails:** Vue evaluates v-model bindings during compilation/render setup, before the v-else condition is fully applied. The explicit v-if with \`editingItem &&\` ensures the binding is only evaluated when the ref exists.
1683
+ When using nullable refs (e.g., \`const editingItem = ref(null)\`) with v-model, use explicit v-if guards:
1684
+ - WRONG: \`<template v-else><input v-model="editingItem.title"/></template>\`
1685
+ - CORRECT: \`<template v-if="editingItem && editingItem.id === item.id"><input v-model="editingItem.title"/></template>\`
1590
1686
 
1591
- **Inline editing pattern (CRUD apps):**
1592
- 1. Create ref: \`const editingItem = ref(null);\`
1593
- 2. View template: \`v-if="!editingItem || editingItem.id !== item.id"\`
1594
- 3. Edit template: \`v-if="editingItem && editingItem.id === item.id"\` (NOT v-else!)
1595
- 4. Start editing: \`editingItem.value = { ...item };\`
1596
- 5. Cancel/save: \`editingItem.value = null;\`
1687
+ Vue evaluates v-model bindings before v-else is applied, causing "Cannot read properties of null" errors.
1597
1688
 
1598
1689
  ## Stack and Business Logic
1599
1690
 
@@ -1601,65 +1692,18 @@ When using a nullable ref (e.g., \`const editingNote = ref(null)\`) with v-model
1601
1692
 
1602
1693
  **All business logic** (controllers, models, middleware, etc.) goes in the tenant DB via MCP tools. If a required capability is not available, use \`request_capability\` to log it.
1603
1694
 
1604
- ## Project Modules (Code Organization)
1605
-
1606
- When building features, group related files by passing a "module" parameter.
1607
-
1608
- - create_file(..., module: "blog-posts") - auto-groups file
1609
- - create_route(..., module: "blog-posts") - auto-groups route
1610
-
1611
- Modules are auto-created if they don't exist. This helps users see all code related to a feature grouped together.
1612
-
1613
- ## CRITICAL: JavaScript Entry File (app.js)
1614
-
1615
- **You MUST have a JS entry file to register Vue components for use on pages.**
1616
-
1617
- ### First component in a project:
1618
- 1. Create app.js in the 'js' directory with the component in includes array:
1619
- - create_file with type='js', extension='js', name='app', includes=[component-file-uuid]
1620
- 2. Add statements for NAMED imports only:
1621
- - "import { createApp } from 'vue';"
1622
- - "createApp(NotesApp).mount('#app');"
1623
- NOTE: The component import (NotesApp) is handled by the includes array, NOT a statement!
1624
- 3. save_file with statement UUIDs
1625
-
1626
- ### Adding more components (app.js already exists):
1627
- 1. **search_files** for "app" to find existing app.js
1628
- 2. **get_file** to retrieve current includes and statements
1629
- 3. Add the new component UUID to the includes array via save_file
1630
- 4. Update the mount code to register the new component:
1631
- - Create new statement: "app.component('Counter', Counter);"
1632
- 5. save_file with updated includes and statements arrays
1695
+ **Prefer Laravel methods:** When Laravel provides a helper (Str, Arr, Hash, Number, Collection), use it instead of native PHP functions.
1633
1696
 
1634
- **DO NOT create duplicate app.js files or duplicate createApp imports!**
1635
- **DO NOT use statements for file imports - use the includes array!**
1697
+ ## JavaScript Entry File (app.js) - Auto-Generated
1636
1698
 
1637
- ### Page mount point (div#app):
1638
- Each page that uses Vue components needs a \`<div id="app"></div>\`.
1699
+ When you create a Vue component, **app.js is automatically created/updated** with component registration. The create_file response will confirm this with an \`appJs\` field.
1639
1700
 
1640
- - **First time on a page:** html_to_elements(page=routeUuid, elements='<div id="app"></div>')
1641
- - **Page already has it:** Do NOT add another one. Check with get_route first if unsure.
1642
-
1643
- **Without app.js AND div#app, Vue components will NOT render!**
1644
-
1645
- ### Import types summary:
1646
- - **File imports (components, classes):** Use \`includes\` array with file UUIDs
1647
- - **Named imports (vue, stellify-framework):** Use statements with add_statement_code
1648
-
1649
- Example app.js structure:
1650
- - includes: [notesAppFileUuid, counterFileUuid]
1651
- - statements: ["import { createApp } from 'vue';", "const app = createApp({});", "app.component('NotesApp', NotesApp);", "app.component('Counter', Counter);", "app.mount('#app');"]
1652
-
1653
- ## Efficiency Tips
1654
-
1655
- - **Move elements between routes:** Use \`update_element\` to change \`routeParent\` - don't delete/recreate
1656
- - **Reparent elements:** Update \`parent\` attribute instead of recreating
1657
- - **Reorder children:** Update parent's \`data\` array with new UUID order
1658
- - Always prefer updating over deleting and recreating`;
1701
+ **Page mount point:** Each page using Vue needs \`<div id="app"></div>\`:
1702
+ - html_to_elements(page=routeUuid, elements='<div id="app"></div>')`;
1659
1703
  // Create MCP server
1660
1704
  const server = new Server({
1661
1705
  name: 'stellify-mcp',
1662
- version: '0.1.26',
1706
+ version: '0.1.28',
1663
1707
  }, {
1664
1708
  capabilities: {
1665
1709
  tools: {},
@@ -1722,14 +1766,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1722
1766
  case 'create_file': {
1723
1767
  const result = await stellify.createFile(args);
1724
1768
  const fileData = result.data || result;
1769
+ const appJs = fileData.appJs || result.appJs;
1770
+ let message = `Created file "${args.name}" (UUID: ${fileData.uuid})`;
1771
+ if (appJs?.created) {
1772
+ message += `. Auto-created app.js (UUID: ${appJs.uuid}) with component registration.`;
1773
+ }
1774
+ else if (appJs?.updated) {
1775
+ message += `. Auto-updated app.js to include this component.`;
1776
+ }
1725
1777
  return {
1726
1778
  content: [
1727
1779
  {
1728
1780
  type: 'text',
1729
1781
  text: JSON.stringify({
1730
1782
  success: true,
1731
- message: `Created file "${args.name}" (UUID: ${fileData.uuid})`,
1783
+ message,
1732
1784
  file: fileData,
1785
+ appJs: appJs || null,
1733
1786
  }, null, 2),
1734
1787
  },
1735
1788
  ],
@@ -2428,6 +2481,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2428
2481
  ],
2429
2482
  };
2430
2483
  }
2484
+ case 'get_setting': {
2485
+ const result = await stellify.getSetting(args.name);
2486
+ const data = result.data || result;
2487
+ return {
2488
+ content: [
2489
+ {
2490
+ type: 'text',
2491
+ text: JSON.stringify({
2492
+ success: true,
2493
+ message: `Retrieved setting "${args.name}"`,
2494
+ setting: data,
2495
+ }, null, 2),
2496
+ },
2497
+ ],
2498
+ };
2499
+ }
2500
+ case 'save_setting': {
2501
+ const { name, data } = args;
2502
+ await stellify.saveSetting(name, data);
2503
+ return {
2504
+ content: [
2505
+ {
2506
+ type: 'text',
2507
+ text: JSON.stringify({
2508
+ success: true,
2509
+ message: `Saved setting "${name}" with ${Object.keys(data).length} key(s)`,
2510
+ keys: Object.keys(data),
2511
+ }, null, 2),
2512
+ },
2513
+ ],
2514
+ };
2515
+ }
2516
+ case 'delete_setting': {
2517
+ await stellify.deleteSetting(args.name);
2518
+ return {
2519
+ content: [
2520
+ {
2521
+ type: 'text',
2522
+ text: JSON.stringify({
2523
+ success: true,
2524
+ message: `Deleted setting "${args.name}"`,
2525
+ }, null, 2),
2526
+ },
2527
+ ],
2528
+ };
2529
+ }
2431
2530
  default:
2432
2531
  throw new Error(`Unknown tool: ${name}`);
2433
2532
  }
@@ -120,6 +120,7 @@ export declare class StellifyClient {
120
120
  elements: string;
121
121
  page?: string;
122
122
  selection?: string;
123
+ file?: string;
123
124
  test?: boolean;
124
125
  }): Promise<any>;
125
126
  getDirectory(uuid: string): Promise<any>;
@@ -183,4 +184,8 @@ export declare class StellifyClient {
183
184
  analyzeQuality(params: {
184
185
  type?: 'full' | 'relationships' | 'fillables' | 'casts' | 'routes';
185
186
  }): Promise<any>;
187
+ getSetting(name: string): Promise<any>;
188
+ saveSetting(name: string, data: Record<string, any>): Promise<any>;
189
+ createSetting(name: string): Promise<any>;
190
+ deleteSetting(name: string): Promise<any>;
186
191
  }
@@ -207,4 +207,22 @@ export class StellifyClient {
207
207
  const response = await this.client.get(endpoint);
208
208
  return response.data;
209
209
  }
210
+ // Settings/Config management - for tenant-specific configuration
211
+ // These settings are read by the config() override in sandbox code execution
212
+ async getSetting(name) {
213
+ const response = await this.client.get(`/config/${name}`);
214
+ return response.data;
215
+ }
216
+ async saveSetting(name, data) {
217
+ const response = await this.client.put(`/config/${name}`, { data });
218
+ return response.data;
219
+ }
220
+ async createSetting(name) {
221
+ const response = await this.client.post('/config', { name });
222
+ return response.data;
223
+ }
224
+ async deleteSetting(name) {
225
+ const response = await this.client.delete(`/config/${name}`);
226
+ return response.data;
227
+ }
210
228
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stellisoft/stellify-mcp",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "mcpName": "io.github.MattStellisoft/stellify-mcp",
5
5
  "description": "MCP server for Stellify - AI-native code generation platform",
6
6
  "main": "dist/index.js",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/Stellify-Software-Ltd/stellify-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.1.26",
9
+ "version": "0.1.28",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@stellisoft/stellify-mcp",
14
- "version": "0.1.26",
14
+ "version": "0.1.28",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },