@enfyra/mcp-server 0.1.53 → 0.1.54

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.
@@ -78,6 +78,7 @@ export declare function buildExtensionUploadModalSnippet(input: any): {
78
78
  };
79
79
  export declare function buildExtensionApiUsageSnippet(input?: AnyRecord): {
80
80
  action: string;
81
+ operation: string;
81
82
  snippet: string;
82
83
  contract: string[];
83
84
  };
@@ -850,12 +850,34 @@ function toPascalIdentifier(value, fallback = 'Items') {
850
850
  export function buildExtensionApiUsageSnippet(input = {}) {
851
851
  const resource = String(input.resource || input.name || 'items');
852
852
  const pascal = toPascalIdentifier(resource, 'Items');
853
- const path = input.path || `/${resource}`;
854
- const method = String(input.method || 'GET').toUpperCase();
853
+ const operation = String(input.operation || input.mode || input.intent || '').toLowerCase() || String(input.method || 'GET').toLowerCase();
854
+ const normalizedOperation = {
855
+ get: 'list',
856
+ read: 'list',
857
+ load: 'list',
858
+ post: 'create',
859
+ patch: 'update',
860
+ put: 'update',
861
+ del: 'delete',
862
+ remove: 'delete',
863
+ destroy: 'delete',
864
+ }[operation] || operation;
865
+ const defaultMethodByOperation = {
866
+ list: 'GET',
867
+ find_one: 'GET',
868
+ create: 'POST',
869
+ update: 'PATCH',
870
+ delete: 'DELETE',
871
+ batch_update: 'PATCH',
872
+ batch_delete: 'DELETE',
873
+ };
874
+ const method = String(input.method || defaultMethodByOperation[normalizedOperation] || 'GET').toUpperCase();
875
+ const rawPath = String(input.path || `/${resource}`);
876
+ const path = rawPath.replace(/\/:id\/?$/, '');
855
877
  const responseName = input.responseName || `${resource}Response`;
856
878
  const pendingName = input.pendingName || `${resource}Pending`;
857
879
  const errorName = input.errorName || `${resource}Error`;
858
- const executeName = input.executeName || (method === 'GET' ? `load${pascal}` : `${method.toLowerCase()}${pascal}`);
880
+ const executeName = input.executeName || (method === 'GET' ? `load${pascal}` : `${normalizedOperation.replace(/(^|_)([a-z])/g, (_m, _p, ch) => ch.toUpperCase()).replace(/^./, (ch) => ch.toLowerCase())}${pascal}Api`);
859
881
  const refreshName = input.refreshName || `refresh${pascal}`;
860
882
  const options = [];
861
883
  if (method !== 'GET')
@@ -879,6 +901,60 @@ export function buildExtensionApiUsageSnippet(input = {}) {
879
901
  lines.push(`onMounted(() => { ${executeName}(); });`);
880
902
  }
881
903
  }
904
+ else if (normalizedOperation === 'create') {
905
+ const handlerName = input.handlerName || `create${pascal.replace(/s$/, '')}`;
906
+ const payloadName = input.payloadName || 'payload';
907
+ lines.push(...[
908
+ '',
909
+ `async function ${handlerName}(${payloadName}) {`,
910
+ ` const response = await ${executeName}({ body: ${payloadName} });`,
911
+ ' if (!response) return null;',
912
+ ' return response;',
913
+ '}',
914
+ ]);
915
+ }
916
+ else if (normalizedOperation === 'update') {
917
+ const handlerName = input.handlerName || `update${pascal.replace(/s$/, '')}`;
918
+ const recordName = input.recordName || 'record';
919
+ const bodyName = input.bodyName || 'body';
920
+ const idExpression = input.idExpression || `${recordName}.id`;
921
+ const bodyArg = bodyName === 'body' ? 'body' : `body: ${bodyName}`;
922
+ lines.push(...[
923
+ '',
924
+ `async function ${handlerName}(${recordName}, ${bodyName}) {`,
925
+ ` const response = await ${executeName}({ id: ${idExpression}, ${bodyArg} });`,
926
+ ' if (!response) return null;',
927
+ ' return response;',
928
+ '}',
929
+ ]);
930
+ }
931
+ else if (normalizedOperation === 'delete') {
932
+ const handlerName = input.handlerName || `delete${pascal.replace(/s$/, '')}`;
933
+ const recordName = input.recordName || 'record';
934
+ const idExpression = input.idExpression || `${recordName}.id`;
935
+ lines.push(...[
936
+ '',
937
+ `async function ${handlerName}(${recordName}) {`,
938
+ ` const response = await ${executeName}({ id: ${idExpression} });`,
939
+ ' if (!response) return null;',
940
+ ' return response;',
941
+ '}',
942
+ ]);
943
+ }
944
+ else if (normalizedOperation === 'batch_update' || normalizedOperation === 'batch_delete') {
945
+ const handlerName = input.handlerName || `${normalizedOperation === 'batch_update' ? 'update' : 'delete'}${pascal}Batch`;
946
+ const idsName = input.idsName || 'ids';
947
+ const bodyName = input.bodyName || 'body';
948
+ const args = normalizedOperation === 'batch_update' ? `{ ids: ${idsName}, body: ${bodyName} }` : `{ ids: ${idsName} }`;
949
+ lines.push(...[
950
+ '',
951
+ `async function ${handlerName}(${normalizedOperation === 'batch_update' ? `${idsName}, ${bodyName}` : idsName}) {`,
952
+ ` const response = await ${executeName}(${args});`,
953
+ ' if (!response) return null;',
954
+ ' return response;',
955
+ '}',
956
+ ]);
957
+ }
882
958
  else {
883
959
  const handlerName = input.handlerName || `${method.toLowerCase()}${pascal}Record`;
884
960
  lines.push(...[
@@ -892,9 +968,11 @@ export function buildExtensionApiUsageSnippet(input = {}) {
892
968
  }
893
969
  return {
894
970
  action: 'extension_api_usage_built',
971
+ operation: normalizedOperation,
895
972
  snippet: lines.join('\n'),
896
973
  contract: [
897
974
  'useApi returns refs plus execute/refresh; it does not auto-run.',
975
+ 'The useApi path is the base route string or a () => string getter; do not pass computed refs and do not put :id placeholders in the path.',
898
976
  'Pass query/body as objects or computed objects, not JSON.stringify strings.',
899
977
  'Read normal list rows from data.value?.data or from the direct execute() response.',
900
978
  'For mutations, call execute({ body }), execute({ id, body }), execute({ id }), or execute({ ids }) from a user action.',
@@ -2848,6 +2926,36 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
2848
2926
  assertExtensionKnowledgeAck(extensionKnowledgeAckKey);
2849
2927
  return jsonText(buildExtensionUiSnippet(kind, input));
2850
2928
  });
2929
+ server.tool('build_extension_api_usage', [
2930
+ 'Generate a contract-safe useApi snippet for Enfyra admin extensions.',
2931
+ 'Use this instead of writing useApi calls from memory so route paths, execute({ id, body }), query/body objects, and mutation handlers follow the app composable contract.',
2932
+ 'The tool returns code only; apply it with patch_extension_code or update_extension_code and then validate/save normally.',
2933
+ ].join(' '), {
2934
+ operation: z.enum(['list', 'find_one', 'create', 'update', 'delete', 'batch_update', 'batch_delete']).default('list').describe('API usage pattern to generate. Reads use the base route with query objects; mutations append ids through execute options.'),
2935
+ resource: z.string().default('items').describe('Resource variable base name, e.g. notes, projects, messages.'),
2936
+ path: z.string().optional().describe('Base API route path such as /notes. Do not include /:id; the builder strips a trailing /:id if provided.'),
2937
+ queryExpression: z.string().optional().describe('Raw Vue expression for query object/computed. Do not JSON.stringify.'),
2938
+ bodyExpression: z.string().optional().describe('Raw Vue expression for default body object/computed when useful. Do not JSON.stringify.'),
2939
+ errorContext: z.string().optional().describe('Safe error context label for useApi error reporting.'),
2940
+ responseName: z.string().optional().describe('Optional data ref variable name.'),
2941
+ pendingName: z.string().optional().describe('Optional pending ref variable name.'),
2942
+ errorName: z.string().optional().describe('Optional error ref variable name.'),
2943
+ executeName: z.string().optional().describe('Optional execute alias name.'),
2944
+ refreshName: z.string().optional().describe('Optional refresh alias name.'),
2945
+ rowsName: z.string().optional().describe('Optional computed rows variable for list/find_one operations.'),
2946
+ handlerName: z.string().optional().describe('Optional generated handler function name for mutations.'),
2947
+ recordName: z.string().optional().describe('Record parameter name for update/delete handlers.'),
2948
+ payloadName: z.string().optional().describe('Payload parameter name for create handlers.'),
2949
+ bodyName: z.string().optional().describe('Body parameter name for update/batch_update handlers.'),
2950
+ idsName: z.string().optional().describe('Ids parameter name for batch handlers.'),
2951
+ idExpression: z.string().optional().describe('Raw id expression for update/delete handlers. Defaults to record.id.'),
2952
+ autoLoad: z.boolean().optional().default(true).describe('For reads, generate onMounted(() => execute()).'),
2953
+ onErrorExpression: z.string().optional().describe('Raw onError handler expression when custom handling is needed.'),
2954
+ extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
2955
+ }, async ({ extensionKnowledgeAckKey, ...input }) => {
2956
+ assertExtensionKnowledgeAck(extensionKnowledgeAckKey);
2957
+ return jsonText(buildExtensionApiUsageSnippet(input));
2958
+ });
2851
2959
  server.tool('build_extension_drawer', [
2852
2960
  'Generate a contract-safe CommonDrawer Vue snippet for Enfyra admin extensions.',
2853
2961
  'Use this before writing or patching drawer/editing workflows so the model does not have to remember CommonDrawer slots, footer action props, full-width fields, or button type rules.',