@xano/developer-mcp 1.0.19 → 1.0.21

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.
@@ -0,0 +1,321 @@
1
+ ---
2
+ applyTo: "middleware/**/*.xs"
3
+ ---
4
+
5
+ # Middleware
6
+
7
+ Middleware intercepts and processes requests before and after your functions, queries, tasks, and tools execute.
8
+
9
+ ## Quick Reference
10
+
11
+ ```xs
12
+ middleware "<name>" {
13
+ description = "What this middleware does"
14
+ exception_policy = "silent" | "rethrow" | "critical"
15
+ response_strategy = "merge" | "replace"
16
+ input { ... }
17
+ stack { ... }
18
+ response = $result
19
+ }
20
+ ```
21
+
22
+ ### Required Blocks
23
+ | Block | Purpose |
24
+ |-------|---------|
25
+ | `input` | Define parameters passed to middleware |
26
+ | `stack` | Processing logic |
27
+ | `response` | Output returned to caller |
28
+
29
+ ### Optional Attributes
30
+ | Attribute | Values | Default | Description |
31
+ |-----------|--------|---------|-------------|
32
+ | `description` | text | - | Documents middleware purpose |
33
+ | `exception_policy` | `silent`, `rethrow`, `critical` | `rethrow` | How to handle errors |
34
+ | `response_strategy` | `merge`, `replace` | `merge` | How response combines with original |
35
+
36
+ ---
37
+
38
+ ## Basic Structure
39
+
40
+ ```xs
41
+ middleware "log_request" {
42
+ description = "Logs all incoming requests"
43
+
44
+ input {
45
+ json request_data
46
+ }
47
+
48
+ stack {
49
+ debug.log {
50
+ value = {
51
+ timestamp: now,
52
+ data: $input.request_data
53
+ }
54
+ }
55
+ }
56
+
57
+ response = null
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Exception Policies
64
+
65
+ Control how middleware handles errors:
66
+
67
+ ### silent
68
+ Errors are caught and ignored. Execution continues normally.
69
+
70
+ ```xs
71
+ middleware "optional_enrichment" {
72
+ exception_policy = "silent"
73
+
74
+ input {
75
+ int user_id
76
+ }
77
+
78
+ stack {
79
+ // If this fails, request continues without enrichment
80
+ api.call "external_service" {
81
+ url = "https://api.example.com/enrich/" ~ $input.user_id
82
+ } as $enriched
83
+ }
84
+
85
+ response = $enriched
86
+ }
87
+ ```
88
+
89
+ ### rethrow (default)
90
+ Errors are passed through to the caller. Standard error handling applies.
91
+
92
+ ```xs
93
+ middleware "validate_token" {
94
+ exception_policy = "rethrow"
95
+
96
+ input {
97
+ text token
98
+ }
99
+
100
+ stack {
101
+ precondition ($input.token|strlen > 0) {
102
+ error_type = "accessdenied"
103
+ error = "Token required"
104
+ }
105
+ }
106
+
107
+ response = { valid: true }
108
+ }
109
+ ```
110
+
111
+ ### critical
112
+ Errors are treated as critical failures. Request is immediately terminated.
113
+
114
+ ```xs
115
+ middleware "security_check" {
116
+ exception_policy = "critical"
117
+
118
+ input {
119
+ text ip_address
120
+ }
121
+
122
+ stack {
123
+ db.query "blocked_ips" {
124
+ where = $db.blocked_ips.ip == $input.ip_address
125
+ return = { type: "exists" }
126
+ } as $is_blocked
127
+
128
+ precondition (!$is_blocked) {
129
+ error_type = "accessdenied"
130
+ error = "Access denied"
131
+ }
132
+ }
133
+
134
+ response = { allowed: true }
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Response Strategies
141
+
142
+ Control how middleware response combines with the original response:
143
+
144
+ ### merge (default)
145
+ Middleware response is merged with original response.
146
+
147
+ ```xs
148
+ middleware "add_metadata" {
149
+ response_strategy = "merge"
150
+
151
+ input {
152
+ }
153
+
154
+ stack {
155
+ var $meta {
156
+ value = {
157
+ server_time: now,
158
+ version: "1.0.0"
159
+ }
160
+ }
161
+ }
162
+
163
+ response = { _meta: $meta }
164
+ }
165
+ // Original: { data: [...] }
166
+ // Result: { data: [...], _meta: { server_time: ..., version: "1.0.0" } }
167
+ ```
168
+
169
+ ### replace
170
+ Middleware response completely replaces original response.
171
+
172
+ ```xs
173
+ middleware "transform_response" {
174
+ response_strategy = "replace"
175
+
176
+ input {
177
+ json original_response
178
+ }
179
+
180
+ stack {
181
+ var $transformed {
182
+ value = {
183
+ success: true,
184
+ payload: $input.original_response
185
+ }
186
+ }
187
+ }
188
+
189
+ response = $transformed
190
+ }
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Common Patterns
196
+
197
+ ### Request Logging
198
+
199
+ ```xs
200
+ middleware "audit_log" {
201
+ description = "Logs all API requests for audit trail"
202
+ exception_policy = "silent"
203
+
204
+ input {
205
+ text endpoint
206
+ text method
207
+ json request_body?
208
+ int user_id?
209
+ }
210
+
211
+ stack {
212
+ db.add "audit_log" {
213
+ data = {
214
+ endpoint: $input.endpoint,
215
+ method: $input.method,
216
+ request_body: $input.request_body,
217
+ user_id: $input.user_id,
218
+ ip_address: $env.$remote_ip,
219
+ created_at: now
220
+ }
221
+ }
222
+ }
223
+
224
+ response = null
225
+ }
226
+ ```
227
+
228
+ ### Rate Limiting
229
+
230
+ ```xs
231
+ middleware "rate_limit" {
232
+ description = "Enforces rate limits per user"
233
+ exception_policy = "rethrow"
234
+
235
+ input {
236
+ int user_id
237
+ int max_requests?=100
238
+ int window_seconds?=60
239
+ }
240
+
241
+ stack {
242
+ var $key { value = "ratelimit:" ~ $input.user_id }
243
+
244
+ redis.incr {
245
+ key = $key
246
+ } as $count
247
+
248
+ conditional {
249
+ if ($count == 1) {
250
+ redis.expire {
251
+ key = $key
252
+ seconds = $input.window_seconds
253
+ }
254
+ }
255
+ }
256
+
257
+ precondition ($count <= $input.max_requests) {
258
+ error_type = "standard"
259
+ error = "Rate limit exceeded"
260
+ }
261
+ }
262
+
263
+ response = { remaining: $input.max_requests - $count }
264
+ }
265
+ ```
266
+
267
+ ### Response Caching
268
+
269
+ ```xs
270
+ middleware "cache_response" {
271
+ description = "Caches responses in Redis"
272
+ exception_policy = "silent"
273
+
274
+ input {
275
+ text cache_key
276
+ int ttl_seconds?=300
277
+ json response_data
278
+ }
279
+
280
+ stack {
281
+ redis.set {
282
+ key = $input.cache_key
283
+ value = $input.response_data|json_encode
284
+ expire = $input.ttl_seconds
285
+ }
286
+ }
287
+
288
+ response = null
289
+ }
290
+ ```
291
+
292
+ ---
293
+
294
+ ## Applying Middleware
295
+
296
+ Middleware is configured at the branch level. See `xanoscript_docs({ topic: "branch" })` for configuration details.
297
+
298
+ ```xs
299
+ branch "production" {
300
+ middleware = {
301
+ query: {
302
+ pre: ["validate_token", "rate_limit"],
303
+ post: ["audit_log", "cache_response"]
304
+ },
305
+ function: {
306
+ pre: ["validate_token"],
307
+ post: ["audit_log"]
308
+ }
309
+ }
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Best Practices
316
+
317
+ 1. **Keep middleware focused** - Each middleware should do one thing well
318
+ 2. **Use appropriate exception policies** - Critical for security, silent for optional enrichment
319
+ 3. **Consider performance** - Middleware runs on every request
320
+ 4. **Log failures** - Even silent failures should be logged for debugging
321
+ 5. **Test independently** - Middleware should be testable in isolation
@@ -42,11 +42,123 @@ api.realtime_event {
42
42
 
43
43
  ---
44
44
 
45
+ ## Realtime Channel Configuration
46
+
47
+ Define channel settings at the workspace level using `realtime_channel`.
48
+
49
+ ```xs
50
+ realtime_channel "<channel_pattern>" {
51
+ description = "Channel description"
52
+ active = true
53
+
54
+ public_messaging = {
55
+ active: true,
56
+ auth: false
57
+ }
58
+
59
+ private_messaging = {
60
+ active: true,
61
+ auth: true
62
+ }
63
+
64
+ settings = {
65
+ anonymous_clients: false,
66
+ nested_channels: true,
67
+ message_history: 100,
68
+ auth_channel: true,
69
+ presence: true
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Channel Configuration Options
75
+
76
+ | Attribute | Type | Description |
77
+ |-----------|------|-------------|
78
+ | `description` | text | Human-readable channel description |
79
+ | `active` | boolean | Enable/disable the channel |
80
+
81
+ ### Messaging Options
82
+
83
+ | Setting | Type | Description |
84
+ |---------|------|-------------|
85
+ | `public_messaging.active` | boolean | Allow public messages |
86
+ | `public_messaging.auth` | boolean | Require authentication for public messages |
87
+ | `private_messaging.active` | boolean | Allow private messages |
88
+ | `private_messaging.auth` | boolean | Require authentication for private messages |
89
+
90
+ ### Settings Options
91
+
92
+ | Setting | Type | Values | Description |
93
+ |---------|------|--------|-------------|
94
+ | `anonymous_clients` | boolean | true/false | Allow unauthenticated clients |
95
+ | `nested_channels` | boolean | true/false | Allow sub-channel patterns |
96
+ | `message_history` | number | 0, 25, 50, 100, 250, 1000 | Messages to retain |
97
+ | `auth_channel` | boolean | true/false | Require channel-level auth |
98
+ | `presence` | boolean | true/false | Track client presence |
99
+
100
+ ### Example Configurations
101
+
102
+ ```xs
103
+ // Chat room channel with presence
104
+ realtime_channel "room:*" {
105
+ description = "Chat room channels"
106
+ active = true
107
+
108
+ public_messaging = { active: true, auth: true }
109
+ private_messaging = { active: true, auth: true }
110
+
111
+ settings = {
112
+ anonymous_clients: false,
113
+ nested_channels: false,
114
+ message_history: 100,
115
+ auth_channel: true,
116
+ presence: true
117
+ }
118
+ }
119
+
120
+ // Global announcements (read-only for clients)
121
+ realtime_channel "announcements" {
122
+ description = "System-wide announcements"
123
+ active = true
124
+
125
+ public_messaging = { active: true, auth: false }
126
+ private_messaging = { active: false, auth: false }
127
+
128
+ settings = {
129
+ anonymous_clients: true,
130
+ nested_channels: false,
131
+ message_history: 25,
132
+ auth_channel: false,
133
+ presence: false
134
+ }
135
+ }
136
+
137
+ // User-specific notifications
138
+ realtime_channel "user:*" {
139
+ description = "User notification channels"
140
+ active = true
141
+
142
+ public_messaging = { active: false, auth: false }
143
+ private_messaging = { active: true, auth: true }
144
+
145
+ settings = {
146
+ anonymous_clients: false,
147
+ nested_channels: false,
148
+ message_history: 50,
149
+ auth_channel: true,
150
+ presence: false
151
+ }
152
+ }
153
+ ```
154
+
155
+ ---
156
+
45
157
  ## Channel Patterns
46
158
 
47
159
  ### Public Channels
48
160
 
49
- Available to all authenticated clients.
161
+ Accessible to all authenticated clients. When you send to a public channel, all clients currently subscribed to that channel receive the message.
50
162
 
51
163
  ```xs
52
164
  // Global announcements
@@ -62,7 +62,7 @@ tool "get_user_by_email" {
62
62
 
63
63
  ## Input Block
64
64
 
65
- For complete type reference, use `xanoscript_docs({ keyword: "input" })`. For tools, input `description` fields are sent to the AI, so write them clearly:
65
+ For complete type reference, use `xanoscript_docs({ topic: "types" })`. For tools, input `description` fields are sent to the AI, so write them clearly:
66
66
 
67
67
  ```xs
68
68
  input {
@@ -62,7 +62,13 @@ input {
62
62
 
63
63
  ### Empty Input Blocks
64
64
 
65
- **CRITICAL:** When an input block has no parameters, the braces MUST be on separate lines. This is a strict syntax requirement - `input {}` on a single line will cause parsing errors.
65
+ **SYNTAX REQUIREMENT:** When an input block has no parameters, the braces MUST be on separate lines.
66
+
67
+ **Why:** The XanoScript parser requires whitespace between braces to distinguish empty input blocks from inline objects.
68
+
69
+ **Impact:** Using `input {}` on a single line will cause:
70
+ - Syntax error during compilation
71
+ - Function/API/tool will fail to deploy
66
72
 
67
73
  ```xs
68
74
  // CORRECT - braces on separate lines
@@ -109,6 +115,21 @@ email contact filters=trim|lower {
109
115
  password secret filters=min:8 // Minimum 8 characters
110
116
  ```
111
117
 
118
+ #### Password Complexity Filters
119
+ | Filter | Description | Example |
120
+ |--------|-------------|---------|
121
+ | `min:<n>` | Minimum total length | `password pwd filters=min:8` |
122
+ | `minAlpha:<n>` | Minimum letters (a-zA-Z) | `password pwd filters=minAlpha:2` |
123
+ | `minLowerAlpha:<n>` | Minimum lowercase letters | `password pwd filters=minLowerAlpha:1` |
124
+ | `minUpperAlpha:<n>` | Minimum uppercase letters | `password pwd filters=minUpperAlpha:1` |
125
+ | `minDigit:<n>` | Minimum digits (0-9) | `password pwd filters=minDigit:1` |
126
+ | `minSymbol:<n>` | Minimum special characters | `password pwd filters=minSymbol:1` |
127
+
128
+ ```xs
129
+ // Strong password: 8+ chars, 1 upper, 1 lower, 1 digit, 1 symbol
130
+ password strong_pwd filters=min:8|minUpperAlpha:1|minLowerAlpha:1|minDigit:1|minSymbol:1
131
+ ```
132
+
112
133
  ### timestamp / date
113
134
  ```xs
114
135
  timestamp created_at?=now // Defaults to current time
@@ -199,10 +220,8 @@ Filters validate and transform input values. Chain with `|`.
199
220
  | `ok:<chars>` | Allow only specified characters | `text hex filters=ok:0123456789abcdef` |
200
221
  | `prevent:<str>` | Block specific substrings | `text name filters=prevent:admin` |
201
222
  | `startsWith:<prefix>` | Require prefix | `text sku filters=startsWith:SKU-` |
202
- | `endsWith:<suffix>` | Require suffix | `text file filters=endsWith:.pdf` |
203
223
  | `alphaOk` | Allow only letters (a-zA-Z) | `text name filters=alphaOk` |
204
224
  | `digitOk` | Allow only digits (0-9) | `text code filters=digitOk` |
205
- | `alphaNumOk` | Allow letters and digits | `text username filters=alphaNumOk` |
206
225
  | `pattern:<regex>` | Match regex pattern | `text phone filters=pattern:^\+?[0-9]+$` |
207
226
 
208
227
  ### Numeric Filters
@@ -210,14 +229,12 @@ Filters validate and transform input values. Chain with `|`.
210
229
  |--------|-------------|---------|
211
230
  | `min:<n>` | Minimum value | `int age filters=min:0` |
212
231
  | `max:<n>` | Maximum value | `int age filters=max:150` |
213
- | `between:<a>:<b>` | Value between a and b | `int score filters=between:0:100` |
214
232
 
215
233
  ### Array Filters
216
234
  | Filter | Description | Example |
217
235
  |--------|-------------|---------|
218
236
  | `min:<n>` | Minimum array length | `text[] tags filters=min:1` |
219
237
  | `max:<n>` | Maximum array length | `text[] tags filters=max:10` |
220
- | `unique` | Remove duplicates | `int[] ids filters=unique` |
221
238
 
222
239
  ### Character Set Filters
223
240
 
@@ -255,11 +272,11 @@ input {
255
272
  ### Combined Examples
256
273
  ```xs
257
274
  input {
258
- text username filters=trim|lower|min:3|max:20|alphaNumOk
275
+ text username filters=trim|lower|min:3|max:20|ok:abcdefghijklmnopqrstuvwxyz0123456789_
259
276
  email contact filters=trim|lower
260
277
  int age filters=min:0|max:150
261
278
  text hex_code filters=ok:abcdef0123456789|min:6|max:6
262
- text[] tags filters=trim|lower|max:50|unique
279
+ text[] tags filters=trim|lower|max:50
263
280
  text phone filters=trim|pattern:^\+?[0-9\s-]+$
264
281
  int quantity filters=min:1|max:100
265
282
  }
@@ -326,7 +343,7 @@ input {
326
343
 
327
344
  ## Validation with Preconditions
328
345
 
329
- For complex validation beyond filters, use preconditions. For complete error handling reference, use `xanoscript_docs({ keyword: "syntax" })`.
346
+ For complex validation beyond filters, use preconditions. For complete error handling reference, use `xanoscript_docs({ topic: "syntax" })`.
330
347
 
331
348
  ```xs
332
349
  precondition ($input.start_date < $input.end_date) {