@enfyra/mcp-server 0.0.92 → 0.0.93

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.92",
3
+ "version": "0.0.93",
4
4
  "description": "MCP server for Enfyra - manage Enfyra instances from MCP-compatible coding tools",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -13,6 +13,7 @@ import { createHash } from 'node:crypto';
13
13
  // Configuration
14
14
  const ENFYRA_API_URL = process.env.ENFYRA_API_URL || 'http://localhost:3000/api';
15
15
  const ENFYRA_API_TOKEN = process.env.ENFYRA_API_TOKEN || '';
16
+ const DISCOVERY_FETCH_TIMEOUT_MS = 12000;
16
17
 
17
18
  // Import modules
18
19
  import { exchangeApiToken, refreshAccessToken, getValidToken, resetTokens, getTokenExpiry, initAuth } from './lib/auth.js';
@@ -360,6 +361,36 @@ function targetInstance() {
360
361
  };
361
362
  }
362
363
 
364
+ async function discoveryFetch(path, { fallbackData = [], timeoutMs = DISCOVERY_FETCH_TIMEOUT_MS } = {}) {
365
+ let timeoutId;
366
+ try {
367
+ const timeout = new Promise((_, reject) => {
368
+ timeoutId = setTimeout(() => {
369
+ reject(new Error(`Discovery request timeout after ${timeoutMs}ms for ${path}`));
370
+ }, timeoutMs);
371
+ });
372
+ return await Promise.race([
373
+ fetchAPI(ENFYRA_API_URL, path),
374
+ timeout,
375
+ ]);
376
+ } catch (error) {
377
+ return {
378
+ statusCode: null,
379
+ success: false,
380
+ error: String(error?.message || error),
381
+ data: fallbackData,
382
+ };
383
+ } finally {
384
+ if (timeoutId) clearTimeout(timeoutId);
385
+ }
386
+ }
387
+
388
+ function collectPartialErrors(results) {
389
+ return Object.entries(results)
390
+ .filter(([, result]) => result?.error)
391
+ .map(([name, result]) => ({ name, error: result.error }));
392
+ }
393
+
363
394
  async function getMetadataTables() {
364
395
  const metadata = await fetchAPI(ENFYRA_API_URL, '/metadata');
365
396
  return {
@@ -628,11 +659,9 @@ server.tool(
628
659
  ].join(' '),
629
660
  {},
630
661
  async () => {
631
- const metadata = await fetchAPI(ENFYRA_API_URL, '/metadata');
632
- const [routesResult, methodsResult] = await Promise.all([
633
- fetchAPI(ENFYRA_API_URL, '/enfyra_route?fields=path,mainTable.name,availableMethods.*,publicMethods.*&limit=1000'),
634
- fetchAPI(ENFYRA_API_URL, '/enfyra_method?limit=100'),
635
- ]);
662
+ const metadata = await discoveryFetch('/metadata');
663
+ const routesResult = await discoveryFetch('/enfyra_route?fields=path,mainTable.name,availableMethods.*,publicMethods.*&limit=1000');
664
+ const methodsResult = await discoveryFetch('/enfyra_method?limit=100');
636
665
 
637
666
  const tables = normalizeTables(metadata);
638
667
  const tableNames = tables.map((table) => table?.name).filter(Boolean).sort();
@@ -647,6 +676,7 @@ server.tool(
647
676
  const payload = {
648
677
  targetInstance: targetInstance(),
649
678
  apiBase: ENFYRA_API_URL.replace(/\/$/, ''),
679
+ partialErrors: collectPartialErrors({ metadata, routesResult, methodsResult }),
650
680
  counts: {
651
681
  tables: tableNames.length,
652
682
  routes: routes.length,
@@ -709,26 +739,15 @@ server.tool(
709
739
  ].join(' '),
710
740
  {},
711
741
  async () => {
712
- const metadata = await fetchAPI(ENFYRA_API_URL, '/metadata');
713
- const [
714
- routesResult,
715
- methodsResult,
716
- gqlResult,
717
- flowsResult,
718
- websocketResult,
719
- storageResult,
720
- settingsResult,
721
- meResult,
722
- ] = await Promise.all([
723
- fetchAPI(ENFYRA_API_URL, '/enfyra_route?fields=path,mainTable.name,availableMethods.*,publicMethods.*,isEnabled&limit=1000'),
724
- fetchAPI(ENFYRA_API_URL, '/enfyra_method?limit=100'),
725
- fetchAPI(ENFYRA_API_URL, '/enfyra_graphql?limit=1000').catch((error) => ({ error: String(error.message || error), data: [] })),
726
- fetchAPI(ENFYRA_API_URL, '/enfyra_flow?limit=1000').catch((error) => ({ error: String(error.message || error), data: [] })),
727
- fetchAPI(ENFYRA_API_URL, '/enfyra_websocket?limit=1000').catch((error) => ({ error: String(error.message || error), data: [] })),
728
- fetchAPI(ENFYRA_API_URL, '/enfyra_storage_config?limit=1000').catch((error) => ({ error: String(error.message || error), data: [] })),
729
- fetchAPI(ENFYRA_API_URL, '/enfyra_setting?limit=1000').catch((error) => ({ error: String(error.message || error), data: [] })),
730
- fetchAPI(ENFYRA_API_URL, '/me').catch((error) => ({ error: String(error.message || error), data: [] })),
731
- ]);
742
+ const metadata = await discoveryFetch('/metadata');
743
+ const routesResult = await discoveryFetch('/enfyra_route?fields=path,mainTable.name,availableMethods.*,publicMethods.*,isEnabled&limit=1000');
744
+ const methodsResult = await discoveryFetch('/enfyra_method?limit=100');
745
+ const gqlResult = await discoveryFetch('/enfyra_graphql?limit=1000');
746
+ const flowsResult = await discoveryFetch('/enfyra_flow?limit=1000');
747
+ const websocketResult = await discoveryFetch('/enfyra_websocket?limit=1000');
748
+ const storageResult = await discoveryFetch('/enfyra_storage_config?limit=1000');
749
+ const settingsResult = await discoveryFetch('/enfyra_setting?limit=1000');
750
+ const meResult = await discoveryFetch('/me', { fallbackData: null });
732
751
 
733
752
  const tables = normalizeTables(metadata);
734
753
  const routes = summarizeRoutes(routesResult);
@@ -739,6 +758,17 @@ server.tool(
739
758
  const payload = {
740
759
  targetInstance: targetInstance(),
741
760
  apiBase: ENFYRA_API_URL.replace(/\/$/, ''),
761
+ partialErrors: collectPartialErrors({
762
+ metadata,
763
+ routesResult,
764
+ methodsResult,
765
+ gqlResult,
766
+ flowsResult,
767
+ websocketResult,
768
+ storageResult,
769
+ settingsResult,
770
+ meResult,
771
+ }),
742
772
  authenticatedUser: Array.isArray(meResult?.data) ? meResult.data[0] || null : meResult?.data || null,
743
773
  database: getMetadataDatabaseContext(metadata, tables),
744
774
  counts: {
@@ -795,8 +825,8 @@ server.tool(
795
825
  tableName: z.string().optional().describe('Optional table name to summarize query fields and relation/deep capabilities.'),
796
826
  },
797
827
  async ({ tableName }) => {
798
- const metadata = await fetchAPI(ENFYRA_API_URL, '/metadata');
799
- const routesResult = await fetchAPI(ENFYRA_API_URL, '/enfyra_route?fields=path,mainTable.name,availableMethods.*,publicMethods.*,isEnabled&limit=1000');
828
+ const metadata = await discoveryFetch('/metadata');
829
+ const routesResult = await discoveryFetch('/enfyra_route?fields=path,mainTable.name,availableMethods.*,publicMethods.*,isEnabled&limit=1000');
800
830
  const tables = normalizeTables(metadata);
801
831
  const routes = summarizeRoutes(routesResult);
802
832
  const table = tableName ? tables.find((item) => item.name === tableName) : null;
@@ -807,6 +837,7 @@ server.tool(
807
837
 
808
838
  const payload = {
809
839
  targetInstance: targetInstance(),
840
+ partialErrors: collectPartialErrors({ metadata, routesResult }),
810
841
  operators: {
811
842
  filter: FILTER_OPERATORS,
812
843
  fieldPermissionConditions: FIELD_PERMISSION_CONDITION_OPERATORS,
@@ -1703,6 +1734,51 @@ async function collectRestDefinitionState() {
1703
1734
  };
1704
1735
  }
1705
1736
 
1737
+ async function collectFeatureSearchState() {
1738
+ const metadata = await discoveryFetch('/metadata');
1739
+ const routesResult = await discoveryFetch('/enfyra_route?limit=500');
1740
+ const handlersResult = await discoveryFetch('/enfyra_route_handler?limit=500');
1741
+ const preHooksResult = await discoveryFetch('/enfyra_pre_hook?limit=500');
1742
+ const postHooksResult = await discoveryFetch('/enfyra_post_hook?limit=500');
1743
+ const routePermissionsResult = await discoveryFetch('/enfyra_route_permission?limit=500');
1744
+ const guardsResult = await discoveryFetch('/enfyra_guard?limit=500');
1745
+ const guardRulesResult = await discoveryFetch('/enfyra_guard_rule?limit=500');
1746
+ const fieldPermissionsResult = await discoveryFetch('/enfyra_field_permission?limit=500');
1747
+ const columnRulesResult = await discoveryFetch('/enfyra_column_rule?limit=500');
1748
+ const methodsResult = await discoveryFetch('/enfyra_method?limit=100');
1749
+ const methodIdNameMap = Object.fromEntries(
1750
+ unwrapData(methodsResult).map((method) => [String(getId(method)), method.name]),
1751
+ );
1752
+
1753
+ return {
1754
+ metadata,
1755
+ tables: normalizeTables(metadata),
1756
+ routes: unwrapData(routesResult),
1757
+ handlers: unwrapData(handlersResult),
1758
+ preHooks: unwrapData(preHooksResult),
1759
+ postHooks: unwrapData(postHooksResult),
1760
+ routePermissions: unwrapData(routePermissionsResult),
1761
+ guards: unwrapData(guardsResult),
1762
+ guardRules: unwrapData(guardRulesResult),
1763
+ fieldPermissions: unwrapData(fieldPermissionsResult),
1764
+ columnRules: unwrapData(columnRulesResult),
1765
+ methodIdNameMap,
1766
+ partialErrors: collectPartialErrors({
1767
+ metadata,
1768
+ routesResult,
1769
+ handlersResult,
1770
+ preHooksResult,
1771
+ postHooksResult,
1772
+ routePermissionsResult,
1773
+ guardsResult,
1774
+ guardRulesResult,
1775
+ fieldPermissionsResult,
1776
+ columnRulesResult,
1777
+ methodsResult,
1778
+ }),
1779
+ };
1780
+ }
1781
+
1706
1782
  function enrichRoute(route, state) {
1707
1783
  const routeId = getId(route);
1708
1784
  const routeHandlers = state.handlers
@@ -1868,7 +1944,7 @@ server.tool(
1868
1944
  throw new Error('inspect_feature query must be at least 2 characters. Use a table name, route path, event name, or specific feature keyword.');
1869
1945
  }
1870
1946
  const max = Math.max(1, Math.min(Number(limit || 8), 25));
1871
- const state = await collectRestDefinitionState();
1947
+ const state = await collectFeatureSearchState();
1872
1948
  const q = rawQuery.toLowerCase();
1873
1949
  const matchesText = (value) => JSON.stringify(value ?? '').toLowerCase().includes(q);
1874
1950
  const tableMatches = state.tables.filter((table) => matchesText({
@@ -1892,6 +1968,7 @@ server.tool(
1892
1968
  targetInstance: targetInstance(),
1893
1969
  query: rawQuery,
1894
1970
  limit: max,
1971
+ partialErrors: state.partialErrors,
1895
1972
  counts: {
1896
1973
  tables: tableMatches.length,
1897
1974
  routes: routeMatches.length,