@xano/developer-mcp 1.0.1 → 1.0.3
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 +96 -31
- package/dist/index.js +248 -180
- package/package.json +4 -2
- package/xanoscript_docs/README.md +107 -1
- package/xanoscript_docs/agents.md +329 -0
- package/xanoscript_docs/apis.md +380 -0
- package/xanoscript_docs/database.md +417 -0
- package/xanoscript_docs/ephemeral.md +333 -0
- package/xanoscript_docs/frontend.md +291 -0
- package/xanoscript_docs/functions.md +232 -2035
- package/xanoscript_docs/integrations.md +439 -0
- package/xanoscript_docs/mcp-servers.md +190 -0
- package/xanoscript_docs/plan.md +192 -0
- package/xanoscript_docs/syntax.md +314 -0
- package/xanoscript_docs/tables.md +270 -0
- package/xanoscript_docs/tasks.md +254 -0
- package/xanoscript_docs/testing.md +335 -0
- package/xanoscript_docs/tools.md +305 -0
- package/xanoscript_docs/types.md +297 -0
- package/xanoscript_docs/version.json +2 -1
- package/xanoscript_docs/api_query_examples.md +0 -1255
- package/xanoscript_docs/api_query_guideline.md +0 -129
- package/xanoscript_docs/build_from_lovable.md +0 -715
- package/xanoscript_docs/db_query_guideline.md +0 -427
- package/xanoscript_docs/ephemeral_environment_guideline.md +0 -529
- package/xanoscript_docs/expression_guideline.md +0 -1086
- package/xanoscript_docs/frontend_guideline.md +0 -67
- package/xanoscript_docs/function_examples.md +0 -1406
- package/xanoscript_docs/function_guideline.md +0 -130
- package/xanoscript_docs/input_guideline.md +0 -227
- package/xanoscript_docs/mcp_server_examples.md +0 -36
- package/xanoscript_docs/mcp_server_guideline.md +0 -69
- package/xanoscript_docs/query_filter.md +0 -489
- package/xanoscript_docs/table_examples.md +0 -586
- package/xanoscript_docs/table_guideline.md +0 -137
- package/xanoscript_docs/task_examples.md +0 -511
- package/xanoscript_docs/task_guideline.md +0 -103
- package/xanoscript_docs/tips_and_tricks.md +0 -144
- package/xanoscript_docs/tool_examples.md +0 -69
- package/xanoscript_docs/tool_guideline.md +0 -139
- package/xanoscript_docs/unit_testing_guideline.md +0 -328
- package/xanoscript_docs/workspace.md +0 -17
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "apis/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# APIs
|
|
6
|
+
|
|
7
|
+
HTTP endpoint definitions in XanoScript.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
```xs
|
|
12
|
+
query "<path>" verb=<METHOD> {
|
|
13
|
+
description = "What this endpoint does"
|
|
14
|
+
auth = "<table>" # Optional: require authentication
|
|
15
|
+
input { ... }
|
|
16
|
+
stack { ... }
|
|
17
|
+
response = $result
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### HTTP Methods
|
|
22
|
+
`GET`, `POST`, `PUT`, `PATCH`, `DELETE`
|
|
23
|
+
|
|
24
|
+
### API Groups (Required)
|
|
25
|
+
|
|
26
|
+
Every API endpoint **must** belong to an API group. An API group is a folder within `apis/` that organizes related endpoints together.
|
|
27
|
+
|
|
28
|
+
- API groups appear as top-level folders under `apis/`
|
|
29
|
+
- Each group **must** have an `api_group.xs` file that defines the group
|
|
30
|
+
- The group contains `.xs` files defining individual endpoints
|
|
31
|
+
- The group name becomes part of the endpoint URL path
|
|
32
|
+
- You cannot create endpoints directly in the `apis/` root folder
|
|
33
|
+
|
|
34
|
+
#### Defining an API Group
|
|
35
|
+
|
|
36
|
+
Create an `api_group.xs` file in the group folder:
|
|
37
|
+
|
|
38
|
+
```xs
|
|
39
|
+
api_group "users" {
|
|
40
|
+
description = "User management endpoints"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### API Group Properties
|
|
45
|
+
|
|
46
|
+
| Property | Description |
|
|
47
|
+
|----------|-------------|
|
|
48
|
+
| `description` | What this API group contains |
|
|
49
|
+
| `canonical` | Optional: custom URL path (overrides folder name) |
|
|
50
|
+
|
|
51
|
+
```xs
|
|
52
|
+
api_group "events" {
|
|
53
|
+
canonical = "events-api" # URL will use /events-api instead of /events
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### File Structure
|
|
58
|
+
```
|
|
59
|
+
apis/
|
|
60
|
+
├── users/ # API group folder
|
|
61
|
+
│ ├── api_group.xs # Required: defines the group
|
|
62
|
+
│ ├── list.xs # GET /users
|
|
63
|
+
│ ├── create.xs # POST /users
|
|
64
|
+
│ └── {id}.xs # GET/PATCH/DELETE /users/{id}
|
|
65
|
+
└── products/ # Another API group
|
|
66
|
+
├── api_group.xs # Required: defines the group
|
|
67
|
+
└── search.xs # GET /products/search
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> **Note:** Files placed directly in `apis/` without a group folder are invalid. Each API group folder must contain an `api_group.xs` file.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Basic Structure
|
|
75
|
+
|
|
76
|
+
```xs
|
|
77
|
+
query "products" verb=GET {
|
|
78
|
+
description = "List all products"
|
|
79
|
+
input {
|
|
80
|
+
int page?=1 filters=min:1
|
|
81
|
+
int per_page?=20 filters=min:1|max:100
|
|
82
|
+
}
|
|
83
|
+
stack {
|
|
84
|
+
db.query "product" {
|
|
85
|
+
return = { type: "list", paging: { page: $input.page, per_page: $input.per_page } }
|
|
86
|
+
} as $products
|
|
87
|
+
}
|
|
88
|
+
response = $products
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Authentication
|
|
95
|
+
|
|
96
|
+
### Public Endpoint (default)
|
|
97
|
+
```xs
|
|
98
|
+
query "status" verb=GET {
|
|
99
|
+
stack { }
|
|
100
|
+
response = { status: "ok" }
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Authenticated Endpoint
|
|
105
|
+
```xs
|
|
106
|
+
query "profile" verb=GET {
|
|
107
|
+
auth = "user" # Requires valid JWT
|
|
108
|
+
stack {
|
|
109
|
+
db.get "user" {
|
|
110
|
+
field_name = "id"
|
|
111
|
+
field_value = $auth.id # User ID from token
|
|
112
|
+
} as $user
|
|
113
|
+
}
|
|
114
|
+
response = $user
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
When `auth` is set:
|
|
119
|
+
- Endpoint requires Bearer token in `Authorization` header
|
|
120
|
+
- `$auth.id` contains authenticated user's ID
|
|
121
|
+
- Invalid/missing token returns 401
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Path Parameters
|
|
126
|
+
|
|
127
|
+
Use `{param}` in the path:
|
|
128
|
+
|
|
129
|
+
```xs
|
|
130
|
+
query "users/{user_id}" verb=GET {
|
|
131
|
+
auth = "user"
|
|
132
|
+
input {
|
|
133
|
+
int user_id { table = "user" }
|
|
134
|
+
}
|
|
135
|
+
stack {
|
|
136
|
+
db.get "user" {
|
|
137
|
+
field_name = "id"
|
|
138
|
+
field_value = $input.user_id
|
|
139
|
+
} as $user
|
|
140
|
+
}
|
|
141
|
+
response = $user
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## CRUD Examples
|
|
148
|
+
|
|
149
|
+
### List (GET)
|
|
150
|
+
```xs
|
|
151
|
+
query "products" verb=GET {
|
|
152
|
+
input {
|
|
153
|
+
text category? filters=trim|lower
|
|
154
|
+
int page?=1
|
|
155
|
+
int per_page?=20
|
|
156
|
+
}
|
|
157
|
+
stack {
|
|
158
|
+
db.query "product" {
|
|
159
|
+
where = $db.product.category ==? $input.category
|
|
160
|
+
sort = { created_at: "desc" }
|
|
161
|
+
return = { type: "list", paging: { page: $input.page, per_page: $input.per_page } }
|
|
162
|
+
} as $products
|
|
163
|
+
}
|
|
164
|
+
response = $products
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Create (POST)
|
|
169
|
+
```xs
|
|
170
|
+
query "products" verb=POST {
|
|
171
|
+
auth = "user"
|
|
172
|
+
input {
|
|
173
|
+
text name filters=trim
|
|
174
|
+
text description? filters=trim
|
|
175
|
+
decimal price filters=min:0
|
|
176
|
+
int category_id { table = "category" }
|
|
177
|
+
}
|
|
178
|
+
stack {
|
|
179
|
+
db.add "product" {
|
|
180
|
+
data = {
|
|
181
|
+
name: $input.name,
|
|
182
|
+
description: $input.description,
|
|
183
|
+
price: $input.price,
|
|
184
|
+
category_id: $input.category_id,
|
|
185
|
+
created_by: $auth.id
|
|
186
|
+
}
|
|
187
|
+
} as $product
|
|
188
|
+
}
|
|
189
|
+
response = $product
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Read (GET with ID)
|
|
194
|
+
```xs
|
|
195
|
+
query "products/{product_id}" verb=GET {
|
|
196
|
+
input {
|
|
197
|
+
int product_id { table = "product" }
|
|
198
|
+
}
|
|
199
|
+
stack {
|
|
200
|
+
db.get "product" {
|
|
201
|
+
field_name = "id"
|
|
202
|
+
field_value = $input.product_id
|
|
203
|
+
} as $product
|
|
204
|
+
|
|
205
|
+
precondition ($product != null) {
|
|
206
|
+
error_type = "notfound"
|
|
207
|
+
error = "Product not found"
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
response = $product
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Update (PATCH)
|
|
215
|
+
```xs
|
|
216
|
+
query "products/{product_id}" verb=PATCH {
|
|
217
|
+
auth = "user"
|
|
218
|
+
input {
|
|
219
|
+
int product_id { table = "product" }
|
|
220
|
+
text name? filters=trim
|
|
221
|
+
text description? filters=trim
|
|
222
|
+
decimal price? filters=min:0
|
|
223
|
+
}
|
|
224
|
+
stack {
|
|
225
|
+
var $updates { value = {} }
|
|
226
|
+
|
|
227
|
+
conditional {
|
|
228
|
+
if ($input.name != null) {
|
|
229
|
+
var.update $updates { value = $updates|set:"name":$input.name }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
conditional {
|
|
233
|
+
if ($input.price != null) {
|
|
234
|
+
var.update $updates { value = $updates|set:"price":$input.price }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
db.patch "product" {
|
|
239
|
+
field_name = "id"
|
|
240
|
+
field_value = $input.product_id
|
|
241
|
+
data = $updates
|
|
242
|
+
} as $product
|
|
243
|
+
}
|
|
244
|
+
response = $product
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Delete (DELETE)
|
|
249
|
+
```xs
|
|
250
|
+
query "products/{product_id}" verb=DELETE {
|
|
251
|
+
auth = "user"
|
|
252
|
+
input {
|
|
253
|
+
int product_id { table = "product" }
|
|
254
|
+
}
|
|
255
|
+
stack {
|
|
256
|
+
db.del "product" {
|
|
257
|
+
field_name = "id"
|
|
258
|
+
field_value = $input.product_id
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
response = { success: true }
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Response Types
|
|
268
|
+
|
|
269
|
+
### JSON (default)
|
|
270
|
+
```xs
|
|
271
|
+
response = $data
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### HTML
|
|
275
|
+
```xs
|
|
276
|
+
stack {
|
|
277
|
+
util.set_header {
|
|
278
|
+
value = "Content-Type: text/html; charset=utf-8"
|
|
279
|
+
duplicates = "replace"
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
util.template_engine {
|
|
283
|
+
value = """
|
|
284
|
+
<html>
|
|
285
|
+
<body><h1>{{ $var.title }}</h1></body>
|
|
286
|
+
</html>
|
|
287
|
+
"""
|
|
288
|
+
} as $html
|
|
289
|
+
}
|
|
290
|
+
response = $html
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Streaming
|
|
294
|
+
```xs
|
|
295
|
+
stack {
|
|
296
|
+
api.stream { value = $processed_data }
|
|
297
|
+
}
|
|
298
|
+
response = null
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Custom Headers
|
|
304
|
+
|
|
305
|
+
```xs
|
|
306
|
+
stack {
|
|
307
|
+
util.set_header {
|
|
308
|
+
value = "X-Custom-Header: value"
|
|
309
|
+
duplicates = "replace"
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
util.set_header {
|
|
313
|
+
value = "Set-Cookie: session=abc123; HttpOnly; Secure"
|
|
314
|
+
duplicates = "add"
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Error Handling
|
|
322
|
+
|
|
323
|
+
### Preconditions
|
|
324
|
+
```xs
|
|
325
|
+
stack {
|
|
326
|
+
precondition ($input.amount > 0) {
|
|
327
|
+
error_type = "inputerror"
|
|
328
|
+
error = "Amount must be positive"
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
precondition ($user != null) {
|
|
332
|
+
error_type = "notfound"
|
|
333
|
+
error = "User not found"
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
precondition ($user.id == $auth.id) {
|
|
337
|
+
error_type = "accessdenied"
|
|
338
|
+
error = "Not authorized"
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Error Types
|
|
344
|
+
| Type | HTTP Status |
|
|
345
|
+
|------|-------------|
|
|
346
|
+
| `inputerror` | 400 Bad Request |
|
|
347
|
+
| `accessdenied` | 403 Forbidden |
|
|
348
|
+
| `notfound` | 404 Not Found |
|
|
349
|
+
| `standard` | 500 Internal Server Error |
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Pagination Response Format
|
|
354
|
+
|
|
355
|
+
When using `return = { type: "list", paging: {...} }`:
|
|
356
|
+
|
|
357
|
+
```json
|
|
358
|
+
{
|
|
359
|
+
"itemsReceived": 20,
|
|
360
|
+
"curPage": 1,
|
|
361
|
+
"nextPage": 2,
|
|
362
|
+
"prevPage": null,
|
|
363
|
+
"offset": 0,
|
|
364
|
+
"perPage": 20,
|
|
365
|
+
"items": [...]
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Best Practices
|
|
372
|
+
|
|
373
|
+
1. **RESTful design** - Use appropriate HTTP methods
|
|
374
|
+
2. **Consistent naming** - Use lowercase, hyphens for multi-word paths
|
|
375
|
+
3. **Authenticate sensitive operations** - Always auth for writes
|
|
376
|
+
4. **Validate inputs** - Use filters and preconditions
|
|
377
|
+
5. **Return appropriate errors** - Use correct error types
|
|
378
|
+
6. **Paginate lists** - Never return unbounded lists
|
|
379
|
+
7. **Document with description** - Explain what endpoint does
|
|
380
|
+
8. **Group related endpoints** - Organize by resource in api groups
|