@enfyra/mcp-server 0.0.92 → 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 +1 -1
- package/src/lib/mcp-instructions.js +1 -0
- package/src/mcp-server-entry.mjs +141 -43
package/package.json
CHANGED
|
@@ -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.',
|
package/src/mcp-server-entry.mjs
CHANGED
|
@@ -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,40 @@ 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
|
+
|
|
394
|
+
function jsonContent(payload, { pretty = false } = {}) {
|
|
395
|
+
return { content: [{ type: 'text', text: JSON.stringify(payload, null, pretty ? 2 : 0) }] };
|
|
396
|
+
}
|
|
397
|
+
|
|
363
398
|
async function getMetadataTables() {
|
|
364
399
|
const metadata = await fetchAPI(ENFYRA_API_URL, '/metadata');
|
|
365
400
|
return {
|
|
@@ -584,7 +619,7 @@ server.tool('get_all_metadata', 'Get concise metadata summary for all tables. Us
|
|
|
584
619
|
...summarizeMetadata(result, { search, limit }),
|
|
585
620
|
detailHint: 'Default response is capped and minimal. Call get_table_metadata({ tableName }) or inspect_table({ tableName }) for columns, relations, and route context.',
|
|
586
621
|
};
|
|
587
|
-
return
|
|
622
|
+
return jsonContent(payload);
|
|
588
623
|
});
|
|
589
624
|
|
|
590
625
|
server.tool('get_table_metadata', 'Get concise metadata for a specific table by name', {
|
|
@@ -601,7 +636,7 @@ server.tool('get_table_metadata', 'Get concise metadata for a specific table by
|
|
|
601
636
|
table: summarizeTable(table),
|
|
602
637
|
queryHint: `Use query_table({ tableName: "${tableName}", fields: [...] }) for records. query_table without fields returns only the primary key.`,
|
|
603
638
|
};
|
|
604
|
-
return
|
|
639
|
+
return jsonContent(payload);
|
|
605
640
|
});
|
|
606
641
|
|
|
607
642
|
server.tool(
|
|
@@ -615,7 +650,7 @@ server.tool(
|
|
|
615
650
|
},
|
|
616
651
|
async ({ category }) => {
|
|
617
652
|
const result = getExamples(category);
|
|
618
|
-
return
|
|
653
|
+
return jsonContent(result);
|
|
619
654
|
},
|
|
620
655
|
);
|
|
621
656
|
|
|
@@ -624,15 +659,14 @@ server.tool(
|
|
|
624
659
|
[
|
|
625
660
|
'Call this first when you need to understand the live Enfyra instance.',
|
|
626
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.',
|
|
627
663
|
'Run broad discovery tools sequentially; do not call multiple broad discovery tools in parallel.',
|
|
628
664
|
].join(' '),
|
|
629
665
|
{},
|
|
630
666
|
async () => {
|
|
631
|
-
const metadata = await
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
fetchAPI(ENFYRA_API_URL, '/enfyra_method?limit=100'),
|
|
635
|
-
]);
|
|
667
|
+
const metadata = await discoveryFetch('/metadata');
|
|
668
|
+
const routesResult = await discoveryFetch('/enfyra_route?fields=path,mainTable.name,availableMethods.*,publicMethods.*&limit=1000');
|
|
669
|
+
const methodsResult = await discoveryFetch('/enfyra_method?limit=100');
|
|
636
670
|
|
|
637
671
|
const tables = normalizeTables(metadata);
|
|
638
672
|
const tableNames = tables.map((table) => table?.name).filter(Boolean).sort();
|
|
@@ -643,10 +677,18 @@ server.tool(
|
|
|
643
677
|
const tableDefinition = tables.find((table) => table?.name === 'enfyra_table');
|
|
644
678
|
const gqlDefinition = tables.find((table) => table?.name === 'enfyra_graphql');
|
|
645
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
|
+
});
|
|
646
687
|
|
|
647
688
|
const payload = {
|
|
648
689
|
targetInstance: targetInstance(),
|
|
649
690
|
apiBase: ENFYRA_API_URL.replace(/\/$/, ''),
|
|
691
|
+
partialErrors: collectPartialErrors({ metadata, routesResult, methodsResult }),
|
|
650
692
|
counts: {
|
|
651
693
|
tables: tableNames.length,
|
|
652
694
|
routes: routes.length,
|
|
@@ -662,10 +704,12 @@ server.tool(
|
|
|
662
704
|
rest: {
|
|
663
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.',
|
|
664
706
|
publicAccess: 'publicMethods controls anonymous REST access per route/method; otherwise Bearer JWT + routePermissions apply.',
|
|
665
|
-
routeTables: routeTableList,
|
|
666
|
-
noRouteTables,
|
|
707
|
+
routeTables: sample(routeTableList),
|
|
708
|
+
noRouteTables: sample(noRouteTableList),
|
|
667
709
|
canonicalCrudTools: 'query_table/create_record/update_record/delete_record use dynamic REST routes and only work for route-backed tables.',
|
|
668
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.',
|
|
669
713
|
},
|
|
670
714
|
schemaManagement: {
|
|
671
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.',
|
|
@@ -693,11 +737,10 @@ server.tool(
|
|
|
693
737
|
: 'Use update_table graphqlEnabled, then reload_graphql if needed.',
|
|
694
738
|
gqlDefinitionColumns: (gqlDefinition?.columns || []).map((column) => column.name),
|
|
695
739
|
},
|
|
696
|
-
tableNames,
|
|
697
|
-
routes,
|
|
740
|
+
tableSamples: sample(tableNames, 40),
|
|
698
741
|
};
|
|
699
742
|
|
|
700
|
-
return
|
|
743
|
+
return jsonContent(payload);
|
|
701
744
|
},
|
|
702
745
|
);
|
|
703
746
|
|
|
@@ -709,36 +752,42 @@ server.tool(
|
|
|
709
752
|
].join(' '),
|
|
710
753
|
{},
|
|
711
754
|
async () => {
|
|
712
|
-
const metadata = await
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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
|
-
]);
|
|
755
|
+
const metadata = await discoveryFetch('/metadata');
|
|
756
|
+
const routesResult = await discoveryFetch('/enfyra_route?fields=path,mainTable.name,availableMethods.*,publicMethods.*,isEnabled&limit=1000');
|
|
757
|
+
const methodsResult = await discoveryFetch('/enfyra_method?limit=100');
|
|
758
|
+
const gqlResult = await discoveryFetch('/enfyra_graphql?limit=1000');
|
|
759
|
+
const flowsResult = await discoveryFetch('/enfyra_flow?limit=1000');
|
|
760
|
+
const websocketResult = await discoveryFetch('/enfyra_websocket?limit=1000');
|
|
761
|
+
const storageResult = await discoveryFetch('/enfyra_storage_config?limit=1000');
|
|
762
|
+
const settingsResult = await discoveryFetch('/enfyra_setting?limit=1000');
|
|
763
|
+
const meResult = await discoveryFetch('/me', { fallbackData: null });
|
|
732
764
|
|
|
733
765
|
const tables = normalizeTables(metadata);
|
|
734
766
|
const routes = summarizeRoutes(routesResult);
|
|
735
767
|
const routeTables = new Set(routes.map((route) => route.mainTable).filter(Boolean));
|
|
736
768
|
const adminRoutes = routes.filter((route) => route.path?.startsWith('/admin'));
|
|
737
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
|
+
});
|
|
738
776
|
|
|
739
777
|
const payload = {
|
|
740
778
|
targetInstance: targetInstance(),
|
|
741
779
|
apiBase: ENFYRA_API_URL.replace(/\/$/, ''),
|
|
780
|
+
partialErrors: collectPartialErrors({
|
|
781
|
+
metadata,
|
|
782
|
+
routesResult,
|
|
783
|
+
methodsResult,
|
|
784
|
+
gqlResult,
|
|
785
|
+
flowsResult,
|
|
786
|
+
websocketResult,
|
|
787
|
+
storageResult,
|
|
788
|
+
settingsResult,
|
|
789
|
+
meResult,
|
|
790
|
+
}),
|
|
742
791
|
authenticatedUser: Array.isArray(meResult?.data) ? meResult.data[0] || null : meResult?.data || null,
|
|
743
792
|
database: getMetadataDatabaseContext(metadata, tables),
|
|
744
793
|
counts: {
|
|
@@ -759,12 +808,12 @@ server.tool(
|
|
|
759
808
|
methods: (methodsResult?.data || []).map((method) => ({ id: method.id || method._id, name: method.name })),
|
|
760
809
|
routeRuntime: {
|
|
761
810
|
routePattern: 'GET/POST /<route-path>; PATCH/DELETE /<route-path>/:id; no dynamic GET /<route-path>/:id.',
|
|
762
|
-
adminRoutes: adminRoutes.map((route) => route.path).sort(),
|
|
763
|
-
publicRoutes: publicRoutes.map((route) => ({
|
|
811
|
+
adminRoutes: sample(adminRoutes.map((route) => route.path).sort()),
|
|
812
|
+
publicRoutes: sample(publicRoutes.map((route) => ({
|
|
764
813
|
path: route.path,
|
|
765
814
|
mainTable: route.mainTable,
|
|
766
815
|
publicMethods: route.publicMethods,
|
|
767
|
-
})),
|
|
816
|
+
}))),
|
|
768
817
|
},
|
|
769
818
|
cacheAndCluster: {
|
|
770
819
|
metadataMutationReloads: 'Metadata-backed mutations emit cache invalidation; admin reload endpoints exist for metadata/routes/graphql/guards/all.',
|
|
@@ -781,7 +830,7 @@ server.tool(
|
|
|
781
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.',
|
|
782
831
|
].filter(Boolean),
|
|
783
832
|
};
|
|
784
|
-
return
|
|
833
|
+
return jsonContent(payload);
|
|
785
834
|
},
|
|
786
835
|
);
|
|
787
836
|
|
|
@@ -795,8 +844,8 @@ server.tool(
|
|
|
795
844
|
tableName: z.string().optional().describe('Optional table name to summarize query fields and relation/deep capabilities.'),
|
|
796
845
|
},
|
|
797
846
|
async ({ tableName }) => {
|
|
798
|
-
const metadata = await
|
|
799
|
-
const routesResult = await
|
|
847
|
+
const metadata = await discoveryFetch('/metadata');
|
|
848
|
+
const routesResult = await discoveryFetch('/enfyra_route?fields=path,mainTable.name,availableMethods.*,publicMethods.*,isEnabled&limit=1000');
|
|
800
849
|
const tables = normalizeTables(metadata);
|
|
801
850
|
const routes = summarizeRoutes(routesResult);
|
|
802
851
|
const table = tableName ? tables.find((item) => item.name === tableName) : null;
|
|
@@ -807,6 +856,7 @@ server.tool(
|
|
|
807
856
|
|
|
808
857
|
const payload = {
|
|
809
858
|
targetInstance: targetInstance(),
|
|
859
|
+
partialErrors: collectPartialErrors({ metadata, routesResult }),
|
|
810
860
|
operators: {
|
|
811
861
|
filter: FILTER_OPERATORS,
|
|
812
862
|
fieldPermissionConditions: FIELD_PERMISSION_CONDITION_OPERATORS,
|
|
@@ -862,7 +912,7 @@ server.tool(
|
|
|
862
912
|
discoveryRule: 'When building a query, inspect table metadata first, then use relation propertyName and primary column from that metadata.',
|
|
863
913
|
};
|
|
864
914
|
|
|
865
|
-
return
|
|
915
|
+
return jsonContent(payload);
|
|
866
916
|
},
|
|
867
917
|
);
|
|
868
918
|
|
|
@@ -1002,7 +1052,7 @@ server.tool(
|
|
|
1002
1052
|
},
|
|
1003
1053
|
};
|
|
1004
1054
|
|
|
1005
|
-
return
|
|
1055
|
+
return jsonContent(payload);
|
|
1006
1056
|
},
|
|
1007
1057
|
);
|
|
1008
1058
|
|
|
@@ -1014,6 +1064,7 @@ server.tool(
|
|
|
1014
1064
|
'get_enfyra_api_context',
|
|
1015
1065
|
[
|
|
1016
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.',
|
|
1017
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}).',
|
|
1018
1069
|
'Auth: publicMethods on a route can allow a method without Bearer; otherwise JWT + routePermissions — see server instructions.',
|
|
1019
1070
|
'If path might differ from table name, use get_all_routes before asserting a URL.',
|
|
@@ -1025,6 +1076,7 @@ server.tool(
|
|
|
1025
1076
|
const base = ENFYRA_API_URL.replace(/\/$/, '');
|
|
1026
1077
|
const gql = buildGraphqlUrls(ENFYRA_API_URL);
|
|
1027
1078
|
const payload = {
|
|
1079
|
+
targetInstance: targetInstance(),
|
|
1028
1080
|
enfyraApiUrl: base,
|
|
1029
1081
|
graphqlHttpUrl: gql.graphqlHttpUrl,
|
|
1030
1082
|
graphqlSchemaUrl: gql.graphqlSchemaUrl,
|
|
@@ -1041,7 +1093,7 @@ server.tool(
|
|
|
1041
1093
|
pathResolution: 'Confirm route path with get_all_routes or metadata — path may not equal table name.',
|
|
1042
1094
|
note: 'Full tool→HTTP mapping is in MCP server instructions (shown to the model at connect).',
|
|
1043
1095
|
};
|
|
1044
|
-
return
|
|
1096
|
+
return jsonContent(payload);
|
|
1045
1097
|
},
|
|
1046
1098
|
);
|
|
1047
1099
|
|
|
@@ -1703,6 +1755,51 @@ async function collectRestDefinitionState() {
|
|
|
1703
1755
|
};
|
|
1704
1756
|
}
|
|
1705
1757
|
|
|
1758
|
+
async function collectFeatureSearchState() {
|
|
1759
|
+
const metadata = await discoveryFetch('/metadata');
|
|
1760
|
+
const routesResult = await discoveryFetch('/enfyra_route?limit=500');
|
|
1761
|
+
const handlersResult = await discoveryFetch('/enfyra_route_handler?limit=500');
|
|
1762
|
+
const preHooksResult = await discoveryFetch('/enfyra_pre_hook?limit=500');
|
|
1763
|
+
const postHooksResult = await discoveryFetch('/enfyra_post_hook?limit=500');
|
|
1764
|
+
const routePermissionsResult = await discoveryFetch('/enfyra_route_permission?limit=500');
|
|
1765
|
+
const guardsResult = await discoveryFetch('/enfyra_guard?limit=500');
|
|
1766
|
+
const guardRulesResult = await discoveryFetch('/enfyra_guard_rule?limit=500');
|
|
1767
|
+
const fieldPermissionsResult = await discoveryFetch('/enfyra_field_permission?limit=500');
|
|
1768
|
+
const columnRulesResult = await discoveryFetch('/enfyra_column_rule?limit=500');
|
|
1769
|
+
const methodsResult = await discoveryFetch('/enfyra_method?limit=100');
|
|
1770
|
+
const methodIdNameMap = Object.fromEntries(
|
|
1771
|
+
unwrapData(methodsResult).map((method) => [String(getId(method)), method.name]),
|
|
1772
|
+
);
|
|
1773
|
+
|
|
1774
|
+
return {
|
|
1775
|
+
metadata,
|
|
1776
|
+
tables: normalizeTables(metadata),
|
|
1777
|
+
routes: unwrapData(routesResult),
|
|
1778
|
+
handlers: unwrapData(handlersResult),
|
|
1779
|
+
preHooks: unwrapData(preHooksResult),
|
|
1780
|
+
postHooks: unwrapData(postHooksResult),
|
|
1781
|
+
routePermissions: unwrapData(routePermissionsResult),
|
|
1782
|
+
guards: unwrapData(guardsResult),
|
|
1783
|
+
guardRules: unwrapData(guardRulesResult),
|
|
1784
|
+
fieldPermissions: unwrapData(fieldPermissionsResult),
|
|
1785
|
+
columnRules: unwrapData(columnRulesResult),
|
|
1786
|
+
methodIdNameMap,
|
|
1787
|
+
partialErrors: collectPartialErrors({
|
|
1788
|
+
metadata,
|
|
1789
|
+
routesResult,
|
|
1790
|
+
handlersResult,
|
|
1791
|
+
preHooksResult,
|
|
1792
|
+
postHooksResult,
|
|
1793
|
+
routePermissionsResult,
|
|
1794
|
+
guardsResult,
|
|
1795
|
+
guardRulesResult,
|
|
1796
|
+
fieldPermissionsResult,
|
|
1797
|
+
columnRulesResult,
|
|
1798
|
+
methodsResult,
|
|
1799
|
+
}),
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1706
1803
|
function enrichRoute(route, state) {
|
|
1707
1804
|
const routeId = getId(route);
|
|
1708
1805
|
const routeHandlers = state.handlers
|
|
@@ -1868,7 +1965,7 @@ server.tool(
|
|
|
1868
1965
|
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
1966
|
}
|
|
1870
1967
|
const max = Math.max(1, Math.min(Number(limit || 8), 25));
|
|
1871
|
-
const state = await
|
|
1968
|
+
const state = await collectFeatureSearchState();
|
|
1872
1969
|
const q = rawQuery.toLowerCase();
|
|
1873
1970
|
const matchesText = (value) => JSON.stringify(value ?? '').toLowerCase().includes(q);
|
|
1874
1971
|
const tableMatches = state.tables.filter((table) => matchesText({
|
|
@@ -1892,6 +1989,7 @@ server.tool(
|
|
|
1892
1989
|
targetInstance: targetInstance(),
|
|
1893
1990
|
query: rawQuery,
|
|
1894
1991
|
limit: max,
|
|
1992
|
+
partialErrors: state.partialErrors,
|
|
1895
1993
|
counts: {
|
|
1896
1994
|
tables: tableMatches.length,
|
|
1897
1995
|
routes: routeMatches.length,
|