@enfyra/mcp-server 0.0.5 → 0.0.7

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": "@enfyra/mcp-server",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "MCP server for Enfyra - manage your Enfyra instance via Claude Code",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -96,14 +96,72 @@ export function buildMcpServerInstructions(apiBaseUrl) {
96
96
  '### Extension (Vue SFC only — NOT React)',
97
97
  '- **CRITICAL:** MUST call `create_record` or `update_record` on `extension_definition` — outputting Vue code in chat does NOT save it. User will NOT see it.',
98
98
  '- **Code format:** Vue SFC only. Structure: `<template>...</template>` + `<script setup>...</script>`. Server auto-compiles; if compile fails, fix and retry.',
99
- '- **NO import statements.** All Vue API and composables are injected globally: `ref`, `reactive`, `computed`, `watch`, `onMounted`; `useToast`, `useApi`, `useEnfyraAuth`, `useRouter`, `useRoute`, `navigateTo`; components `UButton`, `UCard`, `UInput`, `UTable`, `UBadge`, `DataTable`, `FormEditor`, `EmptyState`, `Widget`, etc.',
100
- '- **useApi:** Does NOT auto-run. Must call `execute()` — e.g. `const { data, execute } = useApi(\'/user_definition\', { query: { limit: 10 } }); onMounted(() => execute());`',
101
- '- **useHeaderActionRegistry:** Pass array directly: `useHeaderActionRegistry([{ id: \'refresh\', label: \'Refresh\', onClick: fn, color: \'primary\' }]);`',
102
- '- **type "page":** Requires `menu: { id }` — create menu first (`create_menu` or `create_record` on `menu_definition`), find by path/label, then create extension with `menu: { id: menuId }`. `menu_definition` uses **label** not name — filter by `label` or `path`.',
103
- '- **type "widget":** No menu. Embed via `<Widget :id="extensionId" />`.',
104
- '- **NPM packages:** Before using (e.g. dayjs, chart.js) — check `package_definition` (filter `type: App`, `name`, `isEnabled`). If not found, tell user to install via Settings → Packages. Use `getPackages([\'dayjs\'])` in code; call in `onMounted` or async handler.',
99
+ '- **NO import statements.** All APIs are injected globally (see full list below).',
100
+ '',
101
+ '#### Injected Vue API functions:',
102
+ '- Reactivity: `ref`, `reactive`, `computed`, `readonly`, `shallowRef`, `shallowReactive`',
103
+ '- Lifecycle: `onMounted`, `onUnmounted`, `onBeforeMount`, `onBeforeUnmount`, `onUpdated`, `onBeforeUpdate`',
104
+ '- Watch: `watch`, `watchEffect`',
105
+ '- Component: `defineProps`, `defineEmits`, `defineExpose`, `resolveComponent`, `h`, `defineComponent`',
106
+ '- Utils: `nextTick`, `toRef`, `toRefs`, `unref`, `isRef`, `isProxy`, `isReactive`, `isReadonly`, `toRaw`, `markRaw`, `effectScope`, `getCurrentScope`, `onScopeDispose`',
107
+ '',
108
+ '#### Injected Nuxt composables:',
109
+ '- Router: `useRoute`, `useRouter`, `navigateTo`',
110
+ '- State: `useState`, `useCookie`, `useNuxtApp`, `useRuntimeConfig`',
111
+ '- Data: `useFetch`, `useAsyncData`, `useLazyFetch`',
112
+ '- SEO: `useHead`, `useSeoMeta`',
113
+ '- UI: `useToast`',
114
+ '',
115
+ '#### Injected Enfyra composables:',
116
+ '- **useApi:** API client with auto error handling & toast. Returns `{ data, error, pending, status, execute, refresh }`. Does NOT auto-run — must call `execute()`. Example: `const { data, execute } = useApi(\'/user_definition\', { query: { limit: 10 } }); onMounted(() => execute());`',
117
+ '- **useAuth:** Authentication. Returns `{ me, login, logout, fetchUser, isLoggedIn, isLoading, oauthLogin }`. `me` is reactive user object with `isRootAdmin`, `role`, `allowedRoutePermissions`.',
118
+ '- **usePermissions:** Permission checks. Returns `{ hasPermission(route, method), hasAnyPermission(routes, actions), hasAllPermissions(routes, actions), checkPermissionCondition(condition) }`. Actions: `read`, `create`, `update`, `delete`.',
119
+ '- **useSchema:** Schema management. Returns `{ schemas, schema, fetchSchema, schemaLoading, definition, fieldMap, getField(key), editableFields, generateEmptyForm(), validate(record), getIncludeFields(), useFormChanges() }`.',
120
+ '- **useGlobalState:** Global app state. Returns `{ settings, storageConfigs, aiConfigs, appPackages, sidebarVisible, sidebarCollapsed, routeLoading, toggleSidebar(), setRouteLoading(), fetchAppPackages(), packageCacheState }`.',
121
+ '- **useScreen:** Responsive helpers. Returns `{ width, height, isMobile, isTablet, isDesktop, isLargeDesktop, screenType }`.',
122
+ '- **useConfirm:** Confirmation dialogs. Returns `{ confirm({ title, content, confirmText, cancelText }), isVisible, options, onConfirm, onCancel }`.',
123
+ '- **useHeaderActionRegistry:** Register header actions. Pass array: `useHeaderActionRegistry([{ id, label, onClick, color, icon, order, side, global }])`. Action has `{ id, label, onClick, color, icon, order, side: \'left\'|\'right\', global, component }`.',
124
+ '- **useSubHeaderActionRegistry:** Same as header but for sub-header.',
125
+ '- **useMenuRegistry:** Menu management. Returns `{ menuItems, menuGroups, registerMenuItem, unregisterMenuItem, getMenuItemsBySidebar, findParentMenuIdByPath }`.',
126
+ '- **useMenuApi:** Low-level menu API.',
127
+ '- **useFilterQuery:** Filter builder. Returns `{ buildQuery(filter), buildFilterObject(filter), createEmptyFilter(), hasActiveFilters(filter), getFilterSummary(filter, fields), encodeFilterToUrl(filter), parseFilterFromUrl(searchParams) }`.',
128
+ '- **useDatabase:** Database helpers (returns `{ getId, getIdFieldName }`).',
129
+ '- **useRoutes:** Route management.',
130
+ '- **useHighlight:** Code highlighting.',
131
+ '- **useMounted:** Mount state helper.',
132
+ '',
133
+ '#### Injected UI Components (auto-resolved):',
134
+ '- **Common:** `EmptyState`, `LoadingState`, `ErrorState`, `PageHeader`, `FormCard`, `Modal`, `Drawer`, `BreadCrumbs`, `ListItem`, `LazyImage`, `GlobalConfirm`, `UploadModal`, `UploadModalLazy`, `AvatarInitials`, `BrandingHeader`, `SettingsCard`, `RouteLoading`',
135
+ '- **Data Table:** `DataTable`, `DataTableLazy`, `ColumnSelector`',
136
+ '- **Form:** `FormEditor`, `FilterEditor`, `FilterHistory`, `FieldSelector`',
137
+ '- **File Manager:** `FileManager`, `FileView`, `FileGridCard`, `CreateFolderModal`',
138
+ '- **Menu:** `MenuRenderer`, `MenuItemEditor`',
139
+ '- **UI:** `UButton`, `UCard`, `UInput`, `UTable`, `UBadge` (if available)',
140
+ '- **Extension:** `Widget` — embed widget extension via `<Widget :id="extensionId" />`',
141
+ '- **WebSocket:** `WebSocketManager`',
142
+ '- **Permission:** `PermissionGate`, `PermissionManager`',
143
+ '',
144
+ '#### Global objects:',
145
+ '- `fetch`, `console`, `window`, `document`',
146
+ '- `$ctx` — runtime context',
147
+ '',
148
+ '#### Extension types:',
149
+ '- **type "page":** Full-page extension. Requires `menu: { id }` — create menu first (`create_menu` or `create_record` on `menu_definition`), find by path/label, then create extension with `menu: { id: menuId }`. `menu_definition` uses **label** not name — filter by `label` or `path`.',
150
+ '- **type "widget":** Widget extension. No menu required. Embed via `<Widget :id="extensionId" />` in other extensions or pages.',
151
+ '',
152
+ '#### NPM packages:',
153
+ '- Before using (e.g. dayjs, chart.js) — check `package_definition` (filter `type: App`, `name`, `isEnabled`). If not found, tell user to install via Settings → Packages.',
154
+ '- Use `getPackages([\'dayjs\'])` in code; call in `onMounted` or async handler.',
155
+ '',
156
+ '#### Important patterns:',
157
+ '- **useApi:** Must call `execute()` — does NOT auto-run. Supports batch operations with `ids` or `files` options.',
158
+ '- **Header actions:** `useHeaderActionRegistry([{ id: \'refresh\', label: \'Refresh\', onClick: fn, color: \'primary\', icon: \'lucide:refresh\', order: 0 }])`',
159
+ '- **Schema:** Call `fetchSchema()` first, then use `definition.value`, `editableFields.value`, `getField(\'fieldName\')`.',
160
+ '- **Permissions:** Use `checkPermissionCondition({ or: [{ route: \'/posts\', actions: [\'read\'] }] })` for complex rules.',
105
161
  '- **After create/update:** Tell user to refresh (F5). Changes may not appear until reload.',
106
- '- **Minimal example:** `<template><div class="p-6"><h1 class="text-2xl font-bold">{{ title }}</h1><UButton @click="handleClick">Click</UButton></div></template><script setup>const title = ref(\'My Extension\'); const toast = useToast(); const handleClick = () => toast.add({ title: \'Clicked\', color: \'green\' });</script>`',
162
+ '',
163
+ '#### Minimal example:',
164
+ '`<template><div class="p-6"><h1 class="text-2xl font-bold">{{ title }}</h1><UButton @click="handleClick">Click</UButton></div></template><script setup>const title = ref(\'My Extension\'); const toast = useToast(); const handleClick = () => toast.add({ title: \'Clicked\', color: \'green\' });</script>`',
107
165
  '',
108
166
  '### MCP tool → HTTP',
109
167
  `- \`get_all_metadata\` → GET \`${base}/metadata\``,
@@ -2,7 +2,7 @@
2
2
  * Table & Column tools for Enfyra MCP Server
3
3
  */
4
4
  import { z } from 'zod';
5
- import { fetchAPI, validateTableName } from './fetch.js';
5
+ import { fetchAPI } from './fetch.js';
6
6
 
7
7
  /**
8
8
  * Register table tools with MCP server
@@ -24,7 +24,8 @@ export function registerTableTools(server, ENFYRA_API_URL) {
24
24
  server.tool(
25
25
  'create_table',
26
26
  [
27
- 'Create a new table definition. After creating a table, use create_column to add columns.',
27
+ 'Create a new table definition with an auto-included `id` primary key column.',
28
+ 'Use create_column to add more columns after creation.',
28
29
  'Schema operations (create/update/delete table, add column) must run one at a time — migration locks DB; parallel calls will fail.',
29
30
  'Enfyra auto-creates a REST route at path `/<table_name>` (same segment as `name`, not alias).',
30
31
  'REST surface for that route (matches server route engine): 4 HTTP operations — GET `/<table>` (list/filter), POST `/<table>` (create), PATCH `/<table>/:id` (update), DELETE `/<table>/:id` (delete).',
@@ -39,9 +40,10 @@ export function registerTableTools(server, ENFYRA_API_URL) {
39
40
  isEnabled: z.boolean().optional().default(true).describe('Enable table. Set to false to disable.'),
40
41
  },
41
42
  async ({ name, alias, description, isEnabled }) => {
43
+ const idColumn = { name: 'id', type: 'int', isPrimary: true, isGenerated: true, isNullable: false };
42
44
  const result = await fetchAPI(ENFYRA_API_URL, '/table_definition', {
43
45
  method: 'POST',
44
- body: JSON.stringify({ name, alias, description, isEnabled }),
46
+ body: JSON.stringify({ name, alias, description, isEnabled, columns: [idColumn] }),
45
47
  });
46
48
  const base = ENFYRA_API_URL.replace(/\/$/, '');
47
49
  const routePath = `/${name}`;
@@ -55,6 +57,34 @@ export function registerTableTools(server, ENFYRA_API_URL) {
55
57
  }
56
58
  );
57
59
 
60
+ server.tool(
61
+ 'create_relation',
62
+ [
63
+ 'Create a relation between two tables (many-to-one, one-to-many, one-to-one, many-to-many).',
64
+ 'For many-to-one: a FK column is created on the source table. For one-to-many: the FK is on the target (inverse relation).',
65
+ 'Run sequentially — DB migration locks per operation.',
66
+ ].join(' '),
67
+ {
68
+ sourceTableId: z.string().describe('Source table ID (the table that owns the FK for many-to-one).'),
69
+ targetTableId: z.string().describe('Target table ID.'),
70
+ type: z.enum(['many-to-one', 'one-to-many', 'one-to-one', 'many-to-many']).describe('Relation type.'),
71
+ propertyName: z.string().describe('Property name on source table (e.g., "customer", "items").'),
72
+ inversePropertyName: z.string().optional().describe('Property name on target table for bidirectional relation (e.g., "orders").'),
73
+ isNullable: z.boolean().optional().default(true).describe('Whether the relation is nullable.'),
74
+ onDelete: z.enum(['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION']).optional().default('SET NULL').describe('On delete behavior.'),
75
+ },
76
+ async ({ sourceTableId, targetTableId, type, propertyName, inversePropertyName, isNullable, onDelete }) => {
77
+ const relation = { type, propertyName, inversePropertyName: inversePropertyName || null, isNullable, onDelete };
78
+ const result = await fetchAPI(ENFYRA_API_URL, `/table_definition/${sourceTableId}`, {
79
+ method: 'PATCH',
80
+ body: JSON.stringify({ relations: [{ targetTableId, ...relation }] }),
81
+ });
82
+ return {
83
+ content: [{ type: 'text', text: `Relation created: ${propertyName} (${type}) from table ${sourceTableId} → ${targetTableId}.\n\nFull result:\n${JSON.stringify(result, null, 2)}` }],
84
+ };
85
+ }
86
+ );
87
+
58
88
  server.tool(
59
89
  'create_column',
60
90
  'Create a column for an existing table. Columns cascade through table_definition. Run schema changes sequentially — migration locks DB per operation.',