@enfyra/mcp-server 0.0.86 → 0.0.87
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
package/src/lib/mcp-examples.js
CHANGED
|
@@ -736,6 +736,81 @@ ensure_route_access({
|
|
|
736
736
|
'Route permissions apply when the method is not public.',
|
|
737
737
|
],
|
|
738
738
|
},
|
|
739
|
+
{
|
|
740
|
+
name: 'Rate limit anonymous requests by IP',
|
|
741
|
+
code: `create_guard({
|
|
742
|
+
name: "Public signup IP rate limit",
|
|
743
|
+
path: "/newsletter_signup",
|
|
744
|
+
methods: ["POST"],
|
|
745
|
+
position: "pre_auth",
|
|
746
|
+
isEnabled: true,
|
|
747
|
+
description: "Limit anonymous signup attempts by client IP.",
|
|
748
|
+
rules: JSON.stringify([
|
|
749
|
+
{
|
|
750
|
+
type: "rate_limit_by_ip",
|
|
751
|
+
config: { maxRequests: 10, perSeconds: 60 },
|
|
752
|
+
description: "10 signup attempts per minute per IP"
|
|
753
|
+
}
|
|
754
|
+
])
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
inspect_route({ path: "/newsletter_signup" })
|
|
758
|
+
|
|
759
|
+
test_rest_endpoint({
|
|
760
|
+
method: "POST",
|
|
761
|
+
path: "/newsletter_signup",
|
|
762
|
+
body: { email: "test@example.com" }
|
|
763
|
+
})`,
|
|
764
|
+
notes: [
|
|
765
|
+
'Use pre_auth for anonymous/public route protection because no user is available yet.',
|
|
766
|
+
'Rate-limit configs use maxRequests and perSeconds.',
|
|
767
|
+
'Inspect and test after creation so the final behavior is verified through the actual REST route.',
|
|
768
|
+
],
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
name: 'Rate limit authenticated users',
|
|
772
|
+
code: `create_guard({
|
|
773
|
+
name: "Project create per-user limit",
|
|
774
|
+
path: "/projects",
|
|
775
|
+
methods: ["POST"],
|
|
776
|
+
position: "post_auth",
|
|
777
|
+
isEnabled: true,
|
|
778
|
+
description: "Authenticated users can create at most 3 projects per hour.",
|
|
779
|
+
rules: JSON.stringify([
|
|
780
|
+
{
|
|
781
|
+
type: "rate_limit_by_user",
|
|
782
|
+
config: { maxRequests: 3, perSeconds: 3600 }
|
|
783
|
+
}
|
|
784
|
+
])
|
|
785
|
+
})`,
|
|
786
|
+
notes: [
|
|
787
|
+
'Use post_auth for rate_limit_by_user because the server only has user id after auth and RoleGuard.',
|
|
788
|
+
'This does not grant access; users still need route permissions or a public method to reach the route.',
|
|
789
|
+
'Do not put rate_limit_by_user on pre_auth guards; the server drops that rule from pre-auth trees.',
|
|
790
|
+
],
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
name: 'Restrict an admin-only route to office IPs',
|
|
794
|
+
code: `create_guard({
|
|
795
|
+
name: "Admin reports office allowlist",
|
|
796
|
+
path: "/admin/reports",
|
|
797
|
+
methods: ["GET", "POST"],
|
|
798
|
+
position: "pre_auth",
|
|
799
|
+
isEnabled: false,
|
|
800
|
+
description: "Only office network IPs can reach admin reports.",
|
|
801
|
+
rules: JSON.stringify([
|
|
802
|
+
{
|
|
803
|
+
type: "ip_whitelist",
|
|
804
|
+
config: { ips: ["203.0.113.10", "198.51.100.0/24"] }
|
|
805
|
+
}
|
|
806
|
+
])
|
|
807
|
+
})`,
|
|
808
|
+
notes: [
|
|
809
|
+
'Create risky allowlists disabled first, then inspect the saved guard before enabling it.',
|
|
810
|
+
'IP list configs use ips; exact IPv4 addresses and IPv4 CIDR ranges are supported.',
|
|
811
|
+
'An allowlist is an additional gate, not a replacement for route permissions.',
|
|
812
|
+
],
|
|
813
|
+
},
|
|
739
814
|
{
|
|
740
815
|
name: 'Column rule for email format',
|
|
741
816
|
code: `create_column_rule({
|
|
@@ -82,6 +82,16 @@ export function buildMcpServerInstructions(apiBaseUrl) {
|
|
|
82
82
|
'- **Right pattern:** **`create_route`** without `mainTableId` → optional **`create_handler`** / **`create_pre_hook`** / **`create_post_hook`** on **that route’s id** (from **`get_all_routes`** after create). Handler/hook code must query explicit repos such as `$ctx.$repos.orders`; do not rely on `$repos.main` for custom routes.',
|
|
83
83
|
'- **Handler contract:** `create_handler` takes `routeId`, `method` (or `methods` for batch), `sourceCode`, optional `scriptLanguage`, and optional `timeout`. Do **not** send `logic`, `name`, or `compiledCode`; backend CRUD rejects `logic` and `compiledCode` is generated by the server.',
|
|
84
84
|
'',
|
|
85
|
+
'### Guards (request gates and rate limits)',
|
|
86
|
+
'- Guards are metadata-driven request gates stored in `enfyra_guard` and `enfyra_guard_rule`. Use them for IP allow/deny lists and coarse rate limits before writing custom pre-hooks. Use route permissions for authenticated access, field permissions for field access, and column rules for request body validation.',
|
|
87
|
+
'- Guard execution positions: `pre_auth` runs before JWT/auth and only has client IP + route/method context; use it for anonymous/IP/route-wide gates such as `rate_limit_by_ip`, `rate_limit_by_route`, `ip_whitelist`, and `ip_blacklist`. `post_auth` runs after auth/RoleGuard and has the authenticated user id; use it for `rate_limit_by_user` or rules scoped to specific users.',
|
|
88
|
+
'- Rule types: `rate_limit_by_ip`, `rate_limit_by_user`, `rate_limit_by_route`, `ip_whitelist`, and `ip_blacklist`. Rate-limit rule config is `{"maxRequests": number, "perSeconds": number}`. IP list config is `{"ips": ["203.0.113.10", "198.51.100.0/24"]}`; CIDR matching is IPv4-oriented.',
|
|
89
|
+
'- Root guards can attach to one route through `path` or `routeId`, or globally with `isGlobal: true`. `methods` is an array of HTTP method names; omit it to apply the guard to all methods on that route/global scope. Child guards inherit the root `position`; only root guard `position`, `route`, `methods`, and `isGlobal` determine where it runs.',
|
|
90
|
+
'- Guard `combinator` controls evaluation: `and` rejects on the first failing rule/child; `or` allows when any rule/child passes and rejects only if all fail. Lower `priority` runs first among sibling guards/rules.',
|
|
91
|
+
'- `create_guard` defaults `isEnabled` to `false` to avoid lockouts. For risky global or IP whitelist guards, create disabled first, inspect the saved guard/rules, then enable only after the rule config and route/method scope are confirmed.',
|
|
92
|
+
'- `create_guard` reloads guard cache best-effort after creation. After changing guard metadata, verify behavior with `inspect_route({ path })` and `test_rest_endpoint`; use `/admin/reload/guards` only when verification shows stale guard behavior.',
|
|
93
|
+
'- Do not use `rate_limit_by_user` or user-scoped rule `userIds` on `pre_auth` guards. Server guard cache drops `rate_limit_by_user` from pre-auth trees and ignores user scopes there because no user exists yet.',
|
|
94
|
+
'',
|
|
85
95
|
'### After a new table is created',
|
|
86
96
|
'- MCP **`create_table` supports creating columns and relations in the same call**: pass `columns` and `relations` as JSON arrays. Use `create_relation` only when adding a relation to an existing table later.',
|
|
87
97
|
'- MCP **`create_table` supports `isSingleRecord` directly**. Set `isSingleRecord: true` in the create call for settings/config tables that should keep only one record; do not create first and then patch only for this flag.',
|
package/src/mcp-server-entry.mjs
CHANGED
|
@@ -2593,11 +2593,13 @@ server.tool(
|
|
|
2593
2593
|
'create_guard',
|
|
2594
2594
|
[
|
|
2595
2595
|
'Create a metadata guard with optional rules for REST request gating.',
|
|
2596
|
-
'Root guards attach to route or
|
|
2596
|
+
'Root guards attach to one route by path/routeId or globally with isGlobal. pre_auth runs before JWT and only has IP/route context; post_auth runs after auth and can use user id.',
|
|
2597
|
+
'Rule types: rate_limit_by_ip, rate_limit_by_user, rate_limit_by_route, ip_whitelist, ip_blacklist. Rate limits use {"maxRequests":number,"perSeconds":number}; IP lists use {"ips":["127.0.0.1","10.0.0.0/24"]}.',
|
|
2598
|
+
'Do not use rate_limit_by_user or userIds on pre_auth guards. Create risky global/IP whitelist guards disabled first, then inspect and test before enabling.',
|
|
2597
2599
|
].join(' '),
|
|
2598
2600
|
{
|
|
2599
2601
|
name: z.string().describe('Guard name'),
|
|
2600
|
-
position: z.enum(['pre_auth', 'post_auth']).default('pre_auth').describe('Execution position for root guard'),
|
|
2602
|
+
position: z.enum(['pre_auth', 'post_auth']).default('pre_auth').describe('Execution position for root guard. pre_auth has only IP/route context; post_auth also has authenticated user id.'),
|
|
2601
2603
|
routeId: z.union([z.string(), z.number()]).optional().describe('Optional route id'),
|
|
2602
2604
|
path: z.string().optional().describe('Optional route path'),
|
|
2603
2605
|
methods: z.array(z.string()).optional().describe('Method names this guard applies to. Empty means all configured behavior for route/global.'),
|
|
@@ -2606,7 +2608,7 @@ server.tool(
|
|
|
2606
2608
|
isGlobal: z.boolean().optional().default(false).describe('Apply globally instead of one route'),
|
|
2607
2609
|
isEnabled: z.boolean().optional().default(false).describe('Enable immediately. Default false to avoid accidental lockout.'),
|
|
2608
2610
|
description: z.string().optional().describe('Admin note'),
|
|
2609
|
-
rules: z.string().optional().describe('Optional rules JSON array: [{type, config, priority?, isEnabled?, description?, userIds?}]'),
|
|
2611
|
+
rules: z.string().optional().describe('Optional rules JSON array: [{type, config, priority?, isEnabled?, description?, userIds?}]. Supported types: rate_limit_by_ip, rate_limit_by_user, rate_limit_by_route, ip_whitelist, ip_blacklist.'),
|
|
2610
2612
|
},
|
|
2611
2613
|
async ({ name, position, routeId, path, methods, combinator, priority, isGlobal, isEnabled, description, rules }) => {
|
|
2612
2614
|
let route = null;
|