@enfyra/mcp-server 0.1.30 → 0.1.32
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/README.md +4 -3
- package/dist/lib/mcp-examples.js +25 -0
- package/dist/lib/mcp-examples.js.map +1 -1
- package/dist/lib/mcp-instructions.d.ts +5 -1
- package/dist/lib/mcp-instructions.js +16 -13
- package/dist/lib/mcp-instructions.js.map +1 -1
- package/dist/lib/platform-operation-tools.js +141 -3
- package/dist/lib/platform-operation-tools.js.map +1 -1
- package/dist/lib/required-knowledge.d.ts +1 -1
- package/dist/lib/required-knowledge.js +10 -7
- package/dist/lib/required-knowledge.js.map +1 -1
- package/dist/lib/runtime-zone-tools.d.ts +44 -0
- package/dist/lib/runtime-zone-tools.js +196 -0
- package/dist/lib/runtime-zone-tools.js.map +1 -1
- package/dist/lib/table-tools.d.ts +1 -0
- package/dist/lib/table-tools.js +60 -15
- package/dist/lib/table-tools.js.map +1 -1
- package/dist/lib/tool-routing.d.ts +1 -1
- package/dist/lib/tool-routing.js +42 -28
- package/dist/lib/tool-routing.js.map +1 -1
- package/dist/lib/toolset-filter.d.ts +9 -0
- package/dist/lib/toolset-filter.js +126 -0
- package/dist/lib/toolset-filter.js.map +1 -0
- package/dist/mcp-server-entry.js +50 -17
- package/dist/mcp-server-entry.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const MCP_TOOLSETS: readonly ["guided", "full"];
|
|
2
|
+
export type McpToolset = typeof MCP_TOOLSETS[number];
|
|
3
|
+
export declare function normalizeMcpToolset(value: unknown): McpToolset;
|
|
4
|
+
export declare function isToolVisibleInToolset(toolName: string, toolset: McpToolset): boolean;
|
|
5
|
+
export declare function installToolsetFilter(server: any, toolset: McpToolset): {
|
|
6
|
+
toolset: "full" | "guided";
|
|
7
|
+
hiddenTools: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function summarizeToolsetForInstructions(toolset: McpToolset): string;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export const MCP_TOOLSETS = ['guided', 'full'];
|
|
2
|
+
const GUIDED_TOOL_NAMES = new Set([
|
|
3
|
+
'get_enfyra_required_knowledge',
|
|
4
|
+
'get_enfyra_examples',
|
|
5
|
+
'discover_enfyra_workflows',
|
|
6
|
+
'discover_enfyra_system',
|
|
7
|
+
'discover_runtime_context',
|
|
8
|
+
'discover_query_capabilities',
|
|
9
|
+
'discover_script_contexts',
|
|
10
|
+
'get_enfyra_api_context',
|
|
11
|
+
'get_permission_profile',
|
|
12
|
+
'search_admin_extensions',
|
|
13
|
+
'search_runtime_zone',
|
|
14
|
+
'debug_field_exposure',
|
|
15
|
+
'inspect_table',
|
|
16
|
+
'inspect_route',
|
|
17
|
+
'inspect_feature',
|
|
18
|
+
'trace_metadata_usage',
|
|
19
|
+
'get_table_metadata',
|
|
20
|
+
'get_all_tables',
|
|
21
|
+
'get_all_routes',
|
|
22
|
+
'get_schema_design_context',
|
|
23
|
+
'query_table',
|
|
24
|
+
'count_records',
|
|
25
|
+
'find_one_record',
|
|
26
|
+
'create_records',
|
|
27
|
+
'update_records',
|
|
28
|
+
'delete_records',
|
|
29
|
+
'create_tables',
|
|
30
|
+
'update_tables',
|
|
31
|
+
'delete_tables',
|
|
32
|
+
'create_columns',
|
|
33
|
+
'update_columns',
|
|
34
|
+
'delete_columns',
|
|
35
|
+
'create_relations',
|
|
36
|
+
'delete_relations',
|
|
37
|
+
'get_script_source',
|
|
38
|
+
'patch_script_source',
|
|
39
|
+
'update_script_source',
|
|
40
|
+
'validate_dynamic_script',
|
|
41
|
+
'validate_extension_code',
|
|
42
|
+
'get_extension_theme_contract',
|
|
43
|
+
'get_theme_class_reference',
|
|
44
|
+
'extension_workflow',
|
|
45
|
+
'update_extension_code',
|
|
46
|
+
'patch_extension_code',
|
|
47
|
+
'ensure_menu',
|
|
48
|
+
'reorder_menus',
|
|
49
|
+
'ensure_page_extension',
|
|
50
|
+
'ensure_global_extension',
|
|
51
|
+
'ensure_widget_extension',
|
|
52
|
+
'api_endpoint_workflow',
|
|
53
|
+
'create_pre_hook',
|
|
54
|
+
'test_rest_endpoint',
|
|
55
|
+
'run_admin_test',
|
|
56
|
+
'test_flow_step',
|
|
57
|
+
'trigger_flow',
|
|
58
|
+
'audit_route_access',
|
|
59
|
+
'ensure_route_access',
|
|
60
|
+
'enable_route',
|
|
61
|
+
'disable_route',
|
|
62
|
+
'delete_route',
|
|
63
|
+
'public_route_methods',
|
|
64
|
+
'private_route_methods',
|
|
65
|
+
'set_table_graphql',
|
|
66
|
+
'ensure_column_rule',
|
|
67
|
+
'ensure_field_permission',
|
|
68
|
+
'ensure_guard',
|
|
69
|
+
'ensure_websocket_gateway',
|
|
70
|
+
'ensure_websocket_event',
|
|
71
|
+
'ensure_manual_flow',
|
|
72
|
+
'ensure_scheduled_flow',
|
|
73
|
+
'plan_flow_steps',
|
|
74
|
+
'choose_flow_step_tool',
|
|
75
|
+
'ensure_script_flow_step',
|
|
76
|
+
'ensure_condition_flow_step',
|
|
77
|
+
'ensure_query_flow_step',
|
|
78
|
+
'ensure_http_flow_step',
|
|
79
|
+
'ensure_create_flow_step',
|
|
80
|
+
'ensure_update_flow_step',
|
|
81
|
+
'ensure_delete_flow_step',
|
|
82
|
+
'ensure_sleep_flow_step',
|
|
83
|
+
'ensure_log_flow_step',
|
|
84
|
+
'ensure_trigger_flow_step',
|
|
85
|
+
'search_logs',
|
|
86
|
+
'tail_log',
|
|
87
|
+
'search_npm',
|
|
88
|
+
'install_package',
|
|
89
|
+
]);
|
|
90
|
+
export function normalizeMcpToolset(value) {
|
|
91
|
+
const raw = String(value || '').trim().toLowerCase();
|
|
92
|
+
if (raw === 'full')
|
|
93
|
+
return 'full';
|
|
94
|
+
return 'guided';
|
|
95
|
+
}
|
|
96
|
+
export function isToolVisibleInToolset(toolName, toolset) {
|
|
97
|
+
if (toolset === 'full')
|
|
98
|
+
return true;
|
|
99
|
+
return GUIDED_TOOL_NAMES.has(toolName);
|
|
100
|
+
}
|
|
101
|
+
export function installToolsetFilter(server, toolset) {
|
|
102
|
+
const registerTool = server.tool.bind(server);
|
|
103
|
+
const hiddenTools = [];
|
|
104
|
+
server.tool = (name, description, schema, handler) => {
|
|
105
|
+
if (!isToolVisibleInToolset(name, toolset)) {
|
|
106
|
+
hiddenTools.push(name);
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return registerTool(name, description, schema, handler);
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
toolset,
|
|
113
|
+
hiddenTools,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function summarizeToolsetForInstructions(toolset) {
|
|
117
|
+
if (toolset === 'full') {
|
|
118
|
+
return 'Toolset mode: full. All Enfyra MCP tools are visible, including low-level escape hatches.';
|
|
119
|
+
}
|
|
120
|
+
return [
|
|
121
|
+
'Toolset mode: guided. The visible tool surface is curated for weak and medium LLMs.',
|
|
122
|
+
'Prefer discover_enfyra_workflows, search_runtime_zone, inspect_* tools, and operation-level ensure/workflow tools.',
|
|
123
|
+
'Low-level escape hatches are hidden by default; set ENFYRA_MCP_TOOLSET=full only for expert debugging or compatibility work.',
|
|
124
|
+
].join(' ');
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=toolset-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolset-filter.js","sourceRoot":"","sources":["../../src/lib/toolset-filter.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp-server-entry.js
CHANGED
|
@@ -37,9 +37,11 @@ import { assertDynamicCodeKnowledgeAck, assertDynamicCodeKnowledgeAckIf, assertE
|
|
|
37
37
|
import { validateMainTableRoutePath } from './lib/route-guards.js';
|
|
38
38
|
import { installColumnarToolFormatter, jsonContent } from './lib/response-format.js';
|
|
39
39
|
import { compactSourceFields, writeSourceArtifact } from './lib/source-artifacts.js';
|
|
40
|
+
import { installToolsetFilter, normalizeMcpToolset, summarizeToolsetForInstructions } from './lib/toolset-filter.js';
|
|
40
41
|
import { findRoutePermission, mergeMethodNames, normalizeMethodNames, resolveRoleByNameOrId, routeAvailableMethodNames, routePublicMethodNames, summarizeRouteAccess, summarizeRoutePermission, validateMethodsForRoute, } from './lib/route-permission-tools.js';
|
|
41
42
|
// Initialize auth module
|
|
42
43
|
initAuth(ENFYRA_API_URL, ENFYRA_API_TOKEN);
|
|
44
|
+
const MCP_TOOLSET = normalizeMcpToolset(process.env.ENFYRA_MCP_TOOLSET);
|
|
43
45
|
const CAPABILITY_AREAS = [
|
|
44
46
|
{
|
|
45
47
|
area: 'Schema and metadata',
|
|
@@ -817,9 +819,12 @@ const server = new McpServer({
|
|
|
817
819
|
name: 'enfyra-mcp',
|
|
818
820
|
version: '1.0.0',
|
|
819
821
|
}, {
|
|
820
|
-
instructions: buildMcpServerInstructions(ENFYRA_API_URL
|
|
822
|
+
instructions: buildMcpServerInstructions(ENFYRA_API_URL, {
|
|
823
|
+
toolsetSummary: summarizeToolsetForInstructions(MCP_TOOLSET),
|
|
824
|
+
}),
|
|
821
825
|
});
|
|
822
826
|
installColumnarToolFormatter(server);
|
|
827
|
+
installToolsetFilter(server, MCP_TOOLSET);
|
|
823
828
|
// ============================================================================
|
|
824
829
|
// METADATA TOOLS
|
|
825
830
|
// ============================================================================
|
|
@@ -1308,7 +1313,7 @@ server.tool('get_enfyra_api_context', [
|
|
|
1308
1313
|
};
|
|
1309
1314
|
return jsonContent(payload);
|
|
1310
1315
|
});
|
|
1311
|
-
server.tool('query_table', 'Query any route-backed table. Response is minimal unless fields is explicit. Every call must pass either limit or all=true.', {
|
|
1316
|
+
server.tool('query_table', 'Query any route-backed table. Response is minimal unless fields is explicit. Every call must pass either limit or all=true. Use count_records or meta=filterCount/totalCount for counts; call discover_query_capabilities before using aggregate objects instead of guessing _sum/_count operators.', {
|
|
1312
1317
|
tableName: z.string().describe('Table name to query'),
|
|
1313
1318
|
filter: jsonObjectParam(z, 'Filter object').optional().describe('Filter object. Example: {"status": {"_eq": "active"}}.'),
|
|
1314
1319
|
sort: z.string().optional().describe('Sort field. Prefix with - for descending (e.g., "createdAt", "-id")'),
|
|
@@ -1318,7 +1323,7 @@ server.tool('query_table', 'Query any route-backed table. Response is minimal un
|
|
|
1318
1323
|
fields: z.array(z.string()).optional().describe('Fields to select. If omitted, MCP selects only the table primary key to avoid oversized responses.'),
|
|
1319
1324
|
meta: z.string().optional().describe('Optional REST meta request, e.g. "totalCount", "filterCount", or aggregate modes supported by the route. Use count_records for simple counts.'),
|
|
1320
1325
|
deep: jsonObjectParam(z, 'Deep relation fetch object').optional().describe('Optional deep relation fetch object. Keys must be relation propertyName values.'),
|
|
1321
|
-
aggregate: jsonObjectParam(z, 'Aggregate object').optional().describe('Optional aggregate object keyed by real fields/relations. Results are returned in response.meta.aggregate when supported. Do not request aggregates over hidden fields/private relations in user-facing APIs.'),
|
|
1326
|
+
aggregate: jsonObjectParam(z, 'Aggregate object').optional().describe('Optional aggregate object keyed by real fields/relations, only after discover_query_capabilities confirms the supported operator shape for this table/route. Results are returned in response.meta.aggregate when supported. Do not guess _sum/_count; use count_records or meta=filterCount/totalCount for counts. Do not request aggregates over hidden fields/private relations in user-facing APIs.'),
|
|
1322
1327
|
}, async ({ tableName, filter, sort, page, limit, all, fields, meta, deep, aggregate }) => {
|
|
1323
1328
|
if (!all && limit === undefined) {
|
|
1324
1329
|
throw new Error('query_table requires either limit or all=true. Do not rely on implicit default page sizes.');
|
|
@@ -1673,13 +1678,21 @@ server.tool('update_script_source', [
|
|
|
1673
1678
|
scriptValidation: prepared.scriptValidation,
|
|
1674
1679
|
}, null, 2) }] };
|
|
1675
1680
|
});
|
|
1676
|
-
|
|
1681
|
+
function isNotFoundDeleteError(error) {
|
|
1682
|
+
const message = String(error?.message || error || '').toLowerCase();
|
|
1683
|
+
return message.includes('api error (404)')
|
|
1684
|
+
|| message.includes('not found')
|
|
1685
|
+
|| message.includes('not exists')
|
|
1686
|
+
|| message.includes('does not exist');
|
|
1687
|
+
}
|
|
1688
|
+
server.tool('delete_records', 'Delete one or more route-backed records in one MCP call. Pass items as a native JSON array; for one delete, pass one item. The tool previews every target when confirm=false, rejects duplicate ids, and deletes sequentially when confirm=true. By default, confirm=true skips records that were already removed by cascade or a previous cleanup step.', {
|
|
1677
1689
|
tableName: z.string().describe('Table name'),
|
|
1678
1690
|
items: bulkObjectArrayParam(z, 'Delete items').describe('Native JSON array of delete items: [{ "id": "...", "queryParams": { ... }? }].'),
|
|
1679
1691
|
maxItems: z.number().int().min(1).max(100).optional().default(100).describe('Safety cap for one MCP batch. Default/max is 100.'),
|
|
1680
1692
|
confirm: z.boolean().optional().default(false).describe('Required true to apply destructive deletes. Omit/false returns previews only.'),
|
|
1693
|
+
skipNotFound: z.boolean().optional().default(true).describe('When confirm=true, continue if a target is already gone, for example because a previous delete cascaded child records. Default true.'),
|
|
1681
1694
|
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1682
|
-
}, async ({ tableName, items, maxItems, confirm, globalRulesAckKey }) => {
|
|
1695
|
+
}, async ({ tableName, items, maxItems, confirm, skipNotFound, globalRulesAckKey }) => {
|
|
1683
1696
|
validateTableName(tableName);
|
|
1684
1697
|
const parsedItems = parseBulkItemsArg('items', items);
|
|
1685
1698
|
assertMaxBulkItems('delete_records', parsedItems, maxItems);
|
|
@@ -1718,24 +1731,42 @@ server.tool('delete_records', 'Delete one or more route-backed records in one MC
|
|
|
1718
1731
|
}
|
|
1719
1732
|
assertGlobalRulesAck(globalRulesAckKey);
|
|
1720
1733
|
const deleted = [];
|
|
1734
|
+
const skippedNotFound = [];
|
|
1721
1735
|
for (const [index, item] of parsedItems.entries()) {
|
|
1722
1736
|
const query = parseQueryParamsArg(JSON.stringify(item.queryParams || {}));
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1737
|
+
try {
|
|
1738
|
+
const result = await fetchAPI(ENFYRA_API_URL, appendQuery(`/${tableName}/${encodeURIComponent(String(item.id))}`, query), { method: 'DELETE' });
|
|
1739
|
+
deleted.push({
|
|
1740
|
+
index,
|
|
1741
|
+
id: item.id,
|
|
1742
|
+
statusCode: result?.statusCode,
|
|
1743
|
+
success: result?.success,
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
catch (error) {
|
|
1747
|
+
if (skipNotFound && isNotFoundDeleteError(error)) {
|
|
1748
|
+
skippedNotFound.push({
|
|
1749
|
+
index,
|
|
1750
|
+
id: item.id,
|
|
1751
|
+
skipped: true,
|
|
1752
|
+
reason: 'not_found',
|
|
1753
|
+
});
|
|
1754
|
+
continue;
|
|
1755
|
+
}
|
|
1756
|
+
throw error;
|
|
1757
|
+
}
|
|
1730
1758
|
}
|
|
1731
1759
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
1732
1760
|
action: 'deleted_records',
|
|
1733
1761
|
tableName,
|
|
1734
1762
|
requested: parsedItems.length,
|
|
1735
1763
|
deletedCount: deleted.length,
|
|
1764
|
+
skippedNotFoundCount: skippedNotFound.length,
|
|
1736
1765
|
sequential: true,
|
|
1737
1766
|
duplicateIdsRejected: true,
|
|
1767
|
+
skipNotFound,
|
|
1738
1768
|
deleted,
|
|
1769
|
+
skippedNotFound,
|
|
1739
1770
|
}, null, 2) }] };
|
|
1740
1771
|
});
|
|
1741
1772
|
server.tool('list_methods', 'List enfyra_method records with their UI colors. Use this before creating route methods or method-colored UI.', {}, async () => {
|
|
@@ -2321,13 +2352,13 @@ server.tool('test_rest_endpoint', [
|
|
|
2321
2352
|
};
|
|
2322
2353
|
return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
|
|
2323
2354
|
});
|
|
2324
|
-
server.tool('get_all_routes', 'List route definitions with minimal fields.
|
|
2355
|
+
server.tool('get_all_routes', 'List route definitions with minimal fields. Complete route lists must pass either limit or all=true. If search is provided without limit, the tool returns a bounded lookup window of 10 matches. Call inspect_route for handlers/hooks/permissions detail.', {
|
|
2325
2356
|
includeDisabled: z.boolean().optional().default(false).describe('Include disabled routes'),
|
|
2326
2357
|
search: z.string().optional().describe('Optional path or table substring filter. Use this before creating a route to check duplicates.'),
|
|
2327
|
-
limit: z.number().int().positive().optional().describe('Maximum routes returned after search. Required unless all=true. Do not invent arbitrary limits for "all"; use all=true instead.'),
|
|
2358
|
+
limit: z.number().int().positive().optional().describe('Maximum routes returned after search. Required unless all=true or search is provided. Do not invent arbitrary limits for "all"; use all=true instead.'),
|
|
2328
2359
|
all: z.boolean().optional().default(false).describe('Return all matched routes. Use this when the user asks for all routes or a complete route list.'),
|
|
2329
2360
|
}, async ({ includeDisabled, search, limit, all }) => {
|
|
2330
|
-
if (!all && limit === undefined) {
|
|
2361
|
+
if (!all && limit === undefined && !search?.trim()) {
|
|
2331
2362
|
throw new Error('get_all_routes requires either limit or all=true. Do not rely on implicit default page sizes.');
|
|
2332
2363
|
}
|
|
2333
2364
|
if (all && limit !== undefined) {
|
|
@@ -2348,7 +2379,7 @@ server.tool('get_all_routes', 'List route definitions with minimal fields. Every
|
|
|
2348
2379
|
mainTable: route.mainTable,
|
|
2349
2380
|
}).toLowerCase().includes(q))
|
|
2350
2381
|
: allRoutes;
|
|
2351
|
-
const routeLimit = all ? matchedRoutes.length : limit;
|
|
2382
|
+
const routeLimit = all ? matchedRoutes.length : (limit ?? 10);
|
|
2352
2383
|
const payload = {
|
|
2353
2384
|
statusCode: result?.statusCode,
|
|
2354
2385
|
success: result?.success,
|
|
@@ -2356,6 +2387,7 @@ server.tool('get_all_routes', 'List route definitions with minimal fields. Every
|
|
|
2356
2387
|
matchedRouteCount: matchedRoutes.length,
|
|
2357
2388
|
returnedRouteCount: Math.min(matchedRoutes.length, routeLimit),
|
|
2358
2389
|
all: !!all,
|
|
2390
|
+
implicitSearchLimit: Boolean(!all && limit === undefined && search?.trim()),
|
|
2359
2391
|
complete: all || routeLimit >= matchedRoutes.length,
|
|
2360
2392
|
hardCap: all ? null : routeLimit,
|
|
2361
2393
|
search: search || null,
|
|
@@ -2481,9 +2513,10 @@ server.tool('create_handler', [
|
|
|
2481
2513
|
}, null, 2) }] };
|
|
2482
2514
|
});
|
|
2483
2515
|
server.tool('create_pre_hook', [
|
|
2484
|
-
'Create a pre-hook that runs BEFORE the handler. Use to validate, transform, or
|
|
2516
|
+
'Create a pre-hook that runs BEFORE the handler. Use to validate, transform, inject data, or enforce owner/tenant row filters (RLS).',
|
|
2485
2517
|
'Use `routeId` from `create_route` or `get_all_routes` — do not create a new table just to get a route id.',
|
|
2486
2518
|
'Macros: @BODY, @QUERY, @PARAMS, @USER, @REPOS, @HELPERS, @THROW400..@THROW503.',
|
|
2519
|
+
'For canonical table reads, merge security filters into @QUERY.filter and preserve @QUERY.fields/deep/sort/limit/page/meta/aggregate.',
|
|
2487
2520
|
'If the hook returns a value, that value becomes the response (handler is skipped).',
|
|
2488
2521
|
].join(' '), {
|
|
2489
2522
|
routeId: z.union([z.string(), z.number()]).describe('Route definition ID'),
|