@enfyra/mcp-server 0.1.13 → 0.1.15

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.
Files changed (71) hide show
  1. package/README.md +14 -1
  2. package/dist/index.d.ts +5 -0
  3. package/{src/index.mjs → dist/index.js} +8 -10
  4. package/dist/index.js.map +1 -0
  5. package/dist/lib/auth.d.ts +34 -0
  6. package/dist/lib/auth.js +161 -0
  7. package/dist/lib/auth.js.map +1 -0
  8. package/dist/lib/config-local.d.ts +1 -0
  9. package/dist/lib/config-local.js +719 -0
  10. package/dist/lib/config-local.js.map +1 -0
  11. package/dist/lib/extension-search-tools.d.ts +59 -0
  12. package/dist/lib/extension-search-tools.js +483 -0
  13. package/dist/lib/extension-search-tools.js.map +1 -0
  14. package/dist/lib/fetch.d.ts +28 -0
  15. package/dist/lib/fetch.js +106 -0
  16. package/dist/lib/fetch.js.map +1 -0
  17. package/dist/lib/mcp-examples.d.ts +99 -0
  18. package/dist/lib/mcp-examples.js +2285 -0
  19. package/dist/lib/mcp-examples.js.map +1 -0
  20. package/dist/lib/mcp-instructions.d.ts +11 -0
  21. package/dist/lib/mcp-instructions.js +78 -0
  22. package/dist/lib/mcp-instructions.js.map +1 -0
  23. package/dist/lib/mutation-guards.d.ts +33 -0
  24. package/dist/lib/mutation-guards.js +106 -0
  25. package/dist/lib/mutation-guards.js.map +1 -0
  26. package/dist/lib/platform-operation-tools.d.ts +12 -0
  27. package/dist/lib/platform-operation-tools.js +2304 -0
  28. package/dist/lib/platform-operation-tools.js.map +1 -0
  29. package/dist/lib/required-knowledge.d.ts +32 -0
  30. package/dist/lib/required-knowledge.js +181 -0
  31. package/dist/lib/required-knowledge.js.map +1 -0
  32. package/dist/lib/response-format.d.ts +7 -0
  33. package/dist/lib/response-format.js +179 -0
  34. package/dist/lib/response-format.js.map +1 -0
  35. package/dist/lib/route-guards.d.ts +1 -0
  36. package/dist/lib/route-guards.js +19 -0
  37. package/dist/lib/route-guards.js.map +1 -0
  38. package/dist/lib/route-permission-tools.d.ts +91 -0
  39. package/dist/lib/route-permission-tools.js +151 -0
  40. package/dist/lib/route-permission-tools.js.map +1 -0
  41. package/dist/lib/source-artifacts.d.ts +27 -0
  42. package/dist/lib/source-artifacts.js +82 -0
  43. package/dist/lib/source-artifacts.js.map +1 -0
  44. package/dist/lib/table-tools.d.ts +62 -0
  45. package/dist/lib/table-tools.js +774 -0
  46. package/dist/lib/table-tools.js.map +1 -0
  47. package/dist/lib/tool-routing.d.ts +297 -0
  48. package/dist/lib/tool-routing.js +585 -0
  49. package/dist/lib/tool-routing.js.map +1 -0
  50. package/dist/lib/types.d.ts +17 -0
  51. package/dist/lib/types.js +2 -0
  52. package/dist/lib/types.js.map +1 -0
  53. package/dist/mcp-server-entry.d.ts +4 -0
  54. package/dist/mcp-server-entry.js +2787 -0
  55. package/dist/mcp-server-entry.js.map +1 -0
  56. package/package.json +17 -10
  57. package/src/lib/auth.js +0 -179
  58. package/src/lib/config-local.mjs +0 -718
  59. package/src/lib/fetch.js +0 -111
  60. package/src/lib/mcp-examples.js +0 -2289
  61. package/src/lib/mcp-instructions.js +0 -80
  62. package/src/lib/mutation-guards.js +0 -118
  63. package/src/lib/platform-operation-tools.js +0 -2616
  64. package/src/lib/required-knowledge.js +0 -188
  65. package/src/lib/response-format.js +0 -187
  66. package/src/lib/route-guards.js +0 -24
  67. package/src/lib/route-permission-tools.js +0 -160
  68. package/src/lib/source-artifacts.js +0 -82
  69. package/src/lib/table-tools.js +0 -907
  70. package/src/lib/tool-routing.js +0 -589
  71. package/src/mcp-server-entry.mjs +0 -3177
@@ -1,80 +0,0 @@
1
- /**
2
- * MCP server instructions are sent to the host model on connect.
3
- * Keep this small: route the model to the right discovery/example tools and
4
- * keep only contracts that must be known before any tool call.
5
- */
6
-
7
- /** GraphQL SDL + HTTP endpoint are under the same base as REST. */
8
- export function buildGraphqlUrls(apiBaseUrl) {
9
- const base = String(apiBaseUrl || '').replace(/\/$/, '');
10
- return {
11
- graphqlHttpUrl: `${base}/graphql`,
12
- graphqlSchemaUrl: `${base}/graphql-schema`,
13
- };
14
- }
15
-
16
- export function buildMcpServerInstructions(apiBaseUrl) {
17
- const base = String(apiBaseUrl || '').replace(/\/$/, '');
18
- const { graphqlHttpUrl, graphqlSchemaUrl } = buildGraphqlUrls(apiBaseUrl);
19
-
20
- return [
21
- '## Enfyra MCP',
22
- '',
23
- `API base for this session: \`${base}\`.`,
24
- `GraphQL endpoints: \`${graphqlHttpUrl}\` and \`${graphqlSchemaUrl}\`.`,
25
- '',
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.',
28
- '- When the task intent is clear but the right tool path is not, call `discover_enfyra_workflows` with the intent, risk, and optional surface. Use `detail: "plan"` before writes to get firstTools, required acknowledgements, verify tools, and avoidTools.',
29
- '- 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.',
30
- '- Inspect narrowly. Use `inspect_table`, `inspect_route`, and `inspect_feature` for the table/route/feature being changed instead of loading broad metadata.',
31
- '- Load examples only when needed. Use `get_enfyra_examples` by category. Before extension UI, call `get_extension_theme_contract`; call `get_theme_class_reference` for exact eapp/Nuxt UI theme classes.',
32
- '- For server scripts, call `discover_script_contexts` before writing or reviewing handler/hook/flow/websocket/GraphQL logic.',
33
- '- Before mutating metadata, schema, routes, permissions, menus, packages, cache state, dynamic code, or extension UI, call `get_enfyra_required_knowledge`, read the global rules, and pass `globalRulesAckKey` into write tools. Dynamic server code also requires `dynamicCodeAckKey`; extension code also requires `extensionAckKey`.',
34
- '- With non-root API tokens, call `get_permission_profile` before relying on admin helper tools or when debugging 403s. MCP admin helpers require ordinary route permissions for static admin routes such as `/admin/script/validate`, `/admin/test/run`, `/admin/flow/trigger/:id`, and `/admin/reload/*`.',
35
- '- Prefer the most specific business operation tool over raw metadata CRUD. `discover_enfyra_workflows` provides the current operation-tool map and negative-routing avoidTools.',
36
- '- Before saving standalone dynamic script code, call `validate_dynamic_script` unless the chosen write tool already validates the code. For extension edits, prefer `update_extension_code`, `extension_workflow`, or `ensure_*_extension`; these validate and save atomically. Use `validate_extension_code` only for validation-only checks.',
37
- '- Extension SFCs must use auto-injected components directly in templates, such as `<UButton>`, and must not call `resolveComponent()` for Nuxt UI/eApp components.',
38
- '- For existing script-backed records, use `trace_metadata_usage` then `get_script_source`; edit with `patch_script_source` or `update_script_source` so source is hash-checked and validated.',
39
- '- Validate behavior with `test_rest_endpoint`, `run_admin_test`, `test_flow_step`, or the route-specific tool before claiming a dynamic feature works.',
40
- '',
41
- '### Core Contracts',
42
- '- Tool JSON responses use `responseFormat: "json+columnar-v1"`. If rows are columnar, read values by matching `columns[index]` to `rows[n][index]`; do not guess row keys.',
43
- '- `query_table`, `get_all_routes`, and `get_all_tables` require explicit intent: pass `limit` for bounded reads or `all: true` for a complete list. Do not invent arbitrary limits such as 30 or 50.',
44
- '- Read tools are minimal by default. Pass explicit `fields`; use metadata inspection before guessing field/relation names. Field exclusion mode exists: `fields=-compiledCode`, and `fields=id,-compiledCode` still means all readable fields except `compiledCode`.',
45
- '- Mutations return ids/status by default. Re-read with `find_one_record` or `query_table` and explicit `fields` when the saved row matters.',
46
- '- Dynamic repository reads use `filter`, not `where`: `@REPOS.table.find({ filter: {...} })`, `@REPOS.secure.table.find({ filter: {...} })`, `#table.find({ filter: {...} })`, and `exists(filter)`.',
47
- '- Dynamic repositories have two trust paths. Use secure `@REPOS.main` or `@REPOS.secure.<table>` for user-facing data. `@REPOS.<table>` is trusted/internal and can see hidden fields; never return raw trusted rows to users.',
48
- '- Secure repository choice is not a substitute for authorization. Handlers and hooks still need route access, owner/tenant filters, and explicit checks before returning or mutating records.',
49
- '- Filters, sort helpers, counts, and aggregates over unpublished fields/private relations are sensitive data surfaces; do not expose them in user-facing endpoints.',
50
- '- Use `enfyra_user` as the user table. Model record links as real relations using relation `propertyName` values, not physical FK fields like `userId`, `conversationId`, `senderId`, or `memberId` in generated DB code.',
51
- '- Relation design must stay minimal. Create the owning relation needed for writes/filters first; add `inversePropertyName` only when a concrete response, UI, deep query, aggregate sort/count, or parent-to-child traversal will use that reverse field. For schema work, explicitly review existing relations and mention which inverses are intentionally present or intentionally omitted.',
52
- '- Do not call internal/no-route system tables such as `enfyra_column` or `enfyra_session` through generic CRUD. Use table/column/relation tools and route-backed tables discovered from metadata.',
53
- '- Custom API paths use `api_endpoint_workflow` when a handler is needed and the model should follow returned nextSteps. Use lower-level `create_route` without `mainTableId` only when intentionally creating a route shell; `create_table` is only for new persisted data.',
54
- '- For canonical table reads and RLS, preserve client-controlled query shape: do not override `@QUERY.fields`, `@QUERY.deep`, `@QUERY.sort`, `@QUERY.limit`, `@QUERY.page`, `@QUERY.meta`, `@QUERY.aggregate`, or `debugMode`. Merge only security filters into `@QUERY.filter`.',
55
- '- If a REST read returns a column or relation marked `isPublished=false`, including through dotted relation fields such as `fields=owner.secret` or equivalent `deep` projections, treat it as an Enfyra core support issue. Confirm the minimal repro with `test_rest_endpoint`, tell the user to send a Cloud/support ticket with the table, field path, and response shape, and do not present route-local pre-hooks or frontend hiding as the real fix.',
56
- '- Script source is `sourceCode`; `compiledCode` is generated and may differ textually because macros expand. Do not warn about source/compiled mismatch unless validation or runtime behavior proves the compiled artifact is stale.',
57
- '- For intentional user/domain errors in scripts use `@THROW400`-style helpers or `$ctx.$throw[...]`, not `throw new Error(...)`.',
58
- '- Destructive operations are preview-first. Do not pass `confirm=true` until the user explicitly approves.',
59
- '- Treat permission and security as the first design step for any route, handler, flow, extension, or data surface: decide public/private methods, authenticated route access, owner/tenant scope, and field exposure before writing feature logic.',
60
- '- Enfyra admin UI `usePermissions()` and backend RoleGuard both use route permissions: root admin passes; direct `allowedRoutePermissions` and role `routePermissions` grant route+method access. Use `audit_route_access` and `ensure_route_access` to inspect or grant these permissions.',
61
- '- Route permissions only let authenticated users reach the route after RoleGuard; handlers, hooks, or RLS must still enforce record ownership and tenant/project scope.',
62
- '- Operator posture: act from these contracts plus live metadata. Do not turn expected implementation details into speculative warnings; ask only for new product/design decisions or genuine ambiguity.',
63
- '',
64
- '### App Connection Defaults',
65
- '- Generated Nuxt/Next/SSR apps should use a same-origin proxy such as `/enfyra/**` to the Enfyra API. Browser code calls `/enfyra/login`, `/enfyra/me`, `/enfyra/logout`, and `/enfyra/<table>`; it should not store JWTs.',
66
- '- OAuth starts through the same proxy prefix with `redirect=<absoluteReturnUrl>` and `cookieBridgePrefix=/enfyra`. Provider setup details live in `get_enfyra_examples({ category: "oauth-setup" })`.',
67
- '- Socket.IO browser clients connect to the gateway namespace, e.g. `io("/chat", { path: "/socket.io", withCredentials: true })`, while the app proxies `/socket.io/**` to Enfyra `/ws/socket.io/**`.',
68
- '',
69
- '### Dynamic Script Surface',
70
- '- Prefer macros when available: `@BODY`, `@QUERY`, `@PARAMS`, `@USER`, `@REQ`, `@RES`, `@REPOS`, `@CACHE`, `@HELPERS`, `@FETCH`, `@STORAGE`, `@UPLOADED_FILE`, `@SOCKET`, `@TRIGGER`, `@DATA`, `@ERROR`, `@STATUS`, `@ENV`, `@PKGS`, `@LOGS`, `@SHARE`, `@API`, `@THROW*`, `@FLOW*`, and `#table_name`. Call `discover_script_contexts` for exact per-surface availability.',
71
- '- `@SOCKET.roomSize(room)` is available in the server socket helper. Bound websocket contexts also have `reply`, `join`, `leave`, `disconnect`, `emitToCurrentRoom`, and `broadcastToRoom`; HTTP/flow contexts only have global emit helpers plus `roomSize`.',
72
- '',
73
- '### Direct HTTP Mapping',
74
- '- Route-backed table CRUD is REST: `GET /<table>?...`, `POST /<table>`, `PATCH /<table>/<id>`, `DELETE /<table>/<id>`. There is no `GET /<table>/<id>`; use a filtered list with `limit=1` or `find_one_record`.',
75
- '- REST route lifecycle is controlled by `enfyra_route.isEnabled`: disabled routes are not registered at runtime and return 404. Use `enable_route`/`disable_route` instead of raw route PATCH. REST public access is controlled by route `publicMethods`; otherwise direct HTTP needs Bearer JWT plus route permissions. GraphQL table data requires Bearer auth and table GraphQL enablement; anonymous root/schema probes may still return a 200 without exposing table data.',
76
- '- Admin app page/menu paths such as `/cloud/projects/:id` are UI routes, not Enfyra API endpoints unless an enabled `enfyra_route.path` with the same path exists. Use `test_rest_endpoint` only for paths that are actual API routes under `ENFYRA_API_URL`; verify page extensions through the app URL/browser or by reading the extension/menu metadata.',
77
- '',
78
- 'When the user asks for details, fetch only the relevant live context or example category instead of relying on broad memorized rules.',
79
- ].join('\n');
80
- }
@@ -1,118 +0,0 @@
1
- const SCRIPT_TABLES = new Set([
2
- 'enfyra_route_handler',
3
- 'enfyra_pre_hook',
4
- 'enfyra_post_hook',
5
- 'enfyra_flow_step',
6
- 'enfyra_websocket_event',
7
- 'enfyra_websocket',
8
- 'enfyra_graphql',
9
- 'enfyra_bootstrap_script',
10
- ]);
11
-
12
- const CODE_ALIAS_FORBIDDEN_TABLES = new Set([
13
- 'enfyra_route_handler',
14
- 'enfyra_pre_hook',
15
- 'enfyra_post_hook',
16
- 'enfyra_flow_step',
17
- 'enfyra_websocket_event',
18
- 'enfyra_websocket',
19
- 'enfyra_graphql',
20
- 'enfyra_bootstrap_script',
21
- ]);
22
-
23
- const FORBIDDEN_RELATION_DEFINITION_KEYS = new Set([
24
- 'fkCol',
25
- 'fkColumn',
26
- 'foreignKeyColumn',
27
- 'sourceColumn',
28
- 'targetColumn',
29
- 'junctionSourceColumn',
30
- 'junctionTargetColumn',
31
- ]);
32
-
33
- export function parseRecordData(data) {
34
- const parsed = typeof data === 'string' ? JSON.parse(data) : data;
35
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
36
- throw new Error('Record data must be a JSON object string.');
37
- }
38
- return parsed;
39
- }
40
-
41
- export function getAllowedMutationFields(table) {
42
- const columns = (table?.columns || []).map((column) => column.name).filter(Boolean);
43
- const relations = (table?.relations || []).map((relation) => relation.propertyName).filter(Boolean);
44
- return new Set([...columns, ...relations]);
45
- }
46
-
47
- export function validatePayloadFields(table, payload) {
48
- const allowed = getAllowedMutationFields(table);
49
- if (allowed.size === 0) return;
50
-
51
- const unknown = Object.keys(payload).filter((key) => !allowed.has(key));
52
- if (unknown.length > 0) {
53
- throw new Error(
54
- `Payload contains fields not present in metadata for ${table.name}: ${unknown.join(', ')}. ` +
55
- `Use metadata-backed fields only, or create the field through schema tools first. Known fields: ${[...allowed].sort().join(', ')}`
56
- );
57
- }
58
- }
59
-
60
- export function rejectUnsafeScriptPayload(tableName, payload) {
61
- if (Object.prototype.hasOwnProperty.call(payload, 'compiledCode')) {
62
- throw new Error('Do not send compiledCode. Save sourceCode/scriptLanguage and let Enfyra compile compiledCode.');
63
- }
64
- if (CODE_ALIAS_FORBIDDEN_TABLES.has(tableName) && Object.prototype.hasOwnProperty.call(payload, 'code')) {
65
- throw new Error(`Do not send code to ${tableName}. Use sourceCode/scriptLanguage, or the dedicated MCP create_* tool for this script surface.`);
66
- }
67
- }
68
-
69
- export function rejectUnsafeRelationDefinitionPayload(tableName, payload) {
70
- if (tableName !== 'enfyra_relation') return;
71
- const forbidden = Object.keys(payload).filter((key) => FORBIDDEN_RELATION_DEFINITION_KEYS.has(key));
72
- if (forbidden.length > 0) {
73
- throw new Error(
74
- `Do not send physical FK/junction fields to enfyra_relation: ${forbidden.join(', ')}. ` +
75
- 'Use create_relation with targetTable/type/propertyName; Enfyra derives physical columns.'
76
- );
77
- }
78
- }
79
-
80
- export async function validateScriptSourceIfPresent(fetchAPI, apiUrl, tableName, payload) {
81
- if (!SCRIPT_TABLES.has(tableName) || typeof payload.sourceCode !== 'string') {
82
- return { validated: false, reason: 'no script source' };
83
- }
84
-
85
- try {
86
- const result = await fetchAPI(apiUrl, '/admin/script/validate', {
87
- method: 'POST',
88
- body: JSON.stringify({
89
- sourceCode: payload.sourceCode,
90
- scriptLanguage: payload.scriptLanguage || 'javascript',
91
- }),
92
- });
93
- if (result?.valid === false || result?.success === false) {
94
- throw new Error(result?.error?.message || 'Script validation failed.');
95
- }
96
- return { validated: true, skipped: false };
97
- } catch (error) {
98
- const message = String(error?.message || error);
99
- throw new Error(`Script validation failed before save: ${message}`);
100
- }
101
- }
102
-
103
- export async function prepareRecordMutation({ fetchAPI, apiUrl, tables, tableName, data }) {
104
- const payload = parseRecordData(data);
105
- const table = tables.find((item) => item?.name === tableName || item?.alias === tableName);
106
- if (!table) throw new Error(`Unknown table "${tableName}"`);
107
-
108
- validatePayloadFields(table, payload);
109
- rejectUnsafeScriptPayload(table.name, payload);
110
- rejectUnsafeRelationDefinitionPayload(table.name, payload);
111
- const scriptValidation = await validateScriptSourceIfPresent(fetchAPI, apiUrl, table.name, payload);
112
-
113
- return {
114
- table,
115
- payload,
116
- scriptValidation,
117
- };
118
- }