@enfyra/mcp-server 0.0.93 → 0.0.94

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.93",
3
+ "version": "0.0.94",
4
4
  "description": "MCP server for Enfyra - manage Enfyra instances from MCP-compatible coding tools",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,6 +24,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
24
24
  `GraphQL endpoints: \`${graphqlHttpUrl}\` and \`${graphqlSchemaUrl}\`.`,
25
25
  '',
26
26
  '### Work Flow',
27
+ '- For a quick target/base sanity check, call `get_enfyra_api_context`; do not call broad discovery just to confirm which instance this MCP is connected to.',
27
28
  '- Discover before deciding. For architecture/capability questions call `discover_enfyra_system`; for DB/pk/runtime/cache context call `discover_runtime_context`; for filters/deep/sort/relation query shape call `discover_query_capabilities`. Run broad discovery tools sequentially, not in parallel.',
28
29
  '- Inspect narrowly. Use `inspect_table`, `inspect_route`, and `inspect_feature` for the table/route/feature being changed instead of loading broad metadata.',
29
30
  '- Load examples only when needed. Before generating schemas, app connection code, OAuth, Socket.IO, handlers/hooks, flows, files, guards, permissions, or extensions, call `get_enfyra_examples` with the matching category.',
@@ -391,6 +391,10 @@ function collectPartialErrors(results) {
391
391
  .map(([name, result]) => ({ name, error: result.error }));
392
392
  }
393
393
 
394
+ function jsonContent(payload, { pretty = false } = {}) {
395
+ return { content: [{ type: 'text', text: JSON.stringify(payload, null, pretty ? 2 : 0) }] };
396
+ }
397
+
394
398
  async function getMetadataTables() {
395
399
  const metadata = await fetchAPI(ENFYRA_API_URL, '/metadata');
396
400
  return {
@@ -615,7 +619,7 @@ server.tool('get_all_metadata', 'Get concise metadata summary for all tables. Us
615
619
  ...summarizeMetadata(result, { search, limit }),
616
620
  detailHint: 'Default response is capped and minimal. Call get_table_metadata({ tableName }) or inspect_table({ tableName }) for columns, relations, and route context.',
617
621
  };
618
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
622
+ return jsonContent(payload);
619
623
  });
620
624
 
621
625
  server.tool('get_table_metadata', 'Get concise metadata for a specific table by name', {
@@ -632,7 +636,7 @@ server.tool('get_table_metadata', 'Get concise metadata for a specific table by
632
636
  table: summarizeTable(table),
633
637
  queryHint: `Use query_table({ tableName: "${tableName}", fields: [...] }) for records. query_table without fields returns only the primary key.`,
634
638
  };
635
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
639
+ return jsonContent(payload);
636
640
  });
637
641
 
638
642
  server.tool(
@@ -646,7 +650,7 @@ server.tool(
646
650
  },
647
651
  async ({ category }) => {
648
652
  const result = getExamples(category);
649
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
653
+ return jsonContent(result);
650
654
  },
651
655
  );
652
656
 
@@ -655,6 +659,7 @@ server.tool(
655
659
  [
656
660
  'Call this first when you need to understand the live Enfyra instance.',
657
661
  'Returns a concise capability map from live metadata/routes/method rows, including schema management, REST route behavior, GraphQL enablement, and relation handling.',
662
+ 'Do not use this only to confirm the API base; use get_enfyra_api_context for that cheaper target check.',
658
663
  'Run broad discovery tools sequentially; do not call multiple broad discovery tools in parallel.',
659
664
  ].join(' '),
660
665
  {},
@@ -672,6 +677,13 @@ server.tool(
672
677
  const tableDefinition = tables.find((table) => table?.name === 'enfyra_table');
673
678
  const gqlDefinition = tables.find((table) => table?.name === 'enfyra_graphql');
674
679
  const routeTableList = [...routeTables].sort();
680
+ const noRouteTableList = noRouteTables.sort();
681
+ const sample = (items, max = 40) => ({
682
+ total: items.length,
683
+ returned: Math.min(items.length, max),
684
+ items: items.slice(0, max),
685
+ truncated: items.length > max,
686
+ });
675
687
 
676
688
  const payload = {
677
689
  targetInstance: targetInstance(),
@@ -692,10 +704,12 @@ server.tool(
692
704
  rest: {
693
705
  routePattern: 'Dynamic REST routes expose GET/POST at /<route-path> and PATCH/DELETE at /<route-path>/:id; there is no GET /<route-path>/:id.',
694
706
  publicAccess: 'publicMethods controls anonymous REST access per route/method; otherwise Bearer JWT + routePermissions apply.',
695
- routeTables: routeTableList,
696
- noRouteTables,
707
+ routeTables: sample(routeTableList),
708
+ noRouteTables: sample(noRouteTableList),
697
709
  canonicalCrudTools: 'query_table/create_record/update_record/delete_record use dynamic REST routes and only work for route-backed tables.',
698
710
  customRouteWorkflow: 'For a new endpoint use create_route without mainTableId, then create_handler/create_pre_hook/create_post_hook. Do not create a table just to get a path.',
711
+ routeSamples: sample(routes, 25),
712
+ detailHint: 'Use get_all_routes({ search, limit }) or inspect_route({ path }) for route details. Use inspect_table({ tableName }) for table detail.',
699
713
  },
700
714
  schemaManagement: {
701
715
  createTable: 'POST /enfyra_table supports isSingleRecord at create time and supports columns and relations arrays in the same cascade call. MCP create_table exposes isSingleRecord, columns, and relations directly. It does not accept alias at create time; table name drives the default route/schema behavior.',
@@ -723,11 +737,10 @@ server.tool(
723
737
  : 'Use update_table graphqlEnabled, then reload_graphql if needed.',
724
738
  gqlDefinitionColumns: (gqlDefinition?.columns || []).map((column) => column.name),
725
739
  },
726
- tableNames,
727
- routes,
740
+ tableSamples: sample(tableNames, 40),
728
741
  };
729
742
 
730
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
743
+ return jsonContent(payload);
731
744
  },
732
745
  );
733
746
 
@@ -754,6 +767,12 @@ server.tool(
754
767
  const routeTables = new Set(routes.map((route) => route.mainTable).filter(Boolean));
755
768
  const adminRoutes = routes.filter((route) => route.path?.startsWith('/admin'));
756
769
  const publicRoutes = routes.filter((route) => route.publicMethods?.length);
770
+ const sample = (items, max = 25) => ({
771
+ total: items.length,
772
+ returned: Math.min(items.length, max),
773
+ items: items.slice(0, max),
774
+ truncated: items.length > max,
775
+ });
757
776
 
758
777
  const payload = {
759
778
  targetInstance: targetInstance(),
@@ -789,12 +808,12 @@ server.tool(
789
808
  methods: (methodsResult?.data || []).map((method) => ({ id: method.id || method._id, name: method.name })),
790
809
  routeRuntime: {
791
810
  routePattern: 'GET/POST /<route-path>; PATCH/DELETE /<route-path>/:id; no dynamic GET /<route-path>/:id.',
792
- adminRoutes: adminRoutes.map((route) => route.path).sort(),
793
- publicRoutes: publicRoutes.map((route) => ({
811
+ adminRoutes: sample(adminRoutes.map((route) => route.path).sort()),
812
+ publicRoutes: sample(publicRoutes.map((route) => ({
794
813
  path: route.path,
795
814
  mainTable: route.mainTable,
796
815
  publicMethods: route.publicMethods,
797
- })),
816
+ }))),
798
817
  },
799
818
  cacheAndCluster: {
800
819
  metadataMutationReloads: 'Metadata-backed mutations emit cache invalidation; admin reload endpoints exist for metadata/routes/graphql/guards/all.',
@@ -811,7 +830,7 @@ server.tool(
811
830
  'MCP can test flow steps and websocket scripts through admin test endpoints, but not prove every production queue/client path without a real end-to-end client.',
812
831
  ].filter(Boolean),
813
832
  };
814
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
833
+ return jsonContent(payload);
815
834
  },
816
835
  );
817
836
 
@@ -893,7 +912,7 @@ server.tool(
893
912
  discoveryRule: 'When building a query, inspect table metadata first, then use relation propertyName and primary column from that metadata.',
894
913
  };
895
914
 
896
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
915
+ return jsonContent(payload);
897
916
  },
898
917
  );
899
918
 
@@ -1033,7 +1052,7 @@ server.tool(
1033
1052
  },
1034
1053
  };
1035
1054
 
1036
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
1055
+ return jsonContent(payload);
1037
1056
  },
1038
1057
  );
1039
1058
 
@@ -1045,6 +1064,7 @@ server.tool(
1045
1064
  'get_enfyra_api_context',
1046
1065
  [
1047
1066
  'Returns the resolved API base URL for this MCP session (env ENFYRA_API_URL).',
1067
+ 'Use this as the cheap first target sanity check before broad discovery or mutations.',
1048
1068
  'Use when the user asks which HTTP endpoint or full URL applies: combine enfyraApiUrl with paths from server instructions (GET/POST /{table}, PATCH/DELETE /{table}/{id}, no GET /{table}/{id}).',
1049
1069
  'Auth: publicMethods on a route can allow a method without Bearer; otherwise JWT + routePermissions — see server instructions.',
1050
1070
  'If path might differ from table name, use get_all_routes before asserting a URL.',
@@ -1056,6 +1076,7 @@ server.tool(
1056
1076
  const base = ENFYRA_API_URL.replace(/\/$/, '');
1057
1077
  const gql = buildGraphqlUrls(ENFYRA_API_URL);
1058
1078
  const payload = {
1079
+ targetInstance: targetInstance(),
1059
1080
  enfyraApiUrl: base,
1060
1081
  graphqlHttpUrl: gql.graphqlHttpUrl,
1061
1082
  graphqlSchemaUrl: gql.graphqlSchemaUrl,
@@ -1072,7 +1093,7 @@ server.tool(
1072
1093
  pathResolution: 'Confirm route path with get_all_routes or metadata — path may not equal table name.',
1073
1094
  note: 'Full tool→HTTP mapping is in MCP server instructions (shown to the model at connect).',
1074
1095
  };
1075
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
1096
+ return jsonContent(payload);
1076
1097
  },
1077
1098
  );
1078
1099