@xano/developer-mcp 1.0.1 → 1.0.2
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 +343 -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,335 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "functions/**/*.xs, apis/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Testing
|
|
6
|
+
|
|
7
|
+
Unit tests and mocking in XanoScript.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
```xs
|
|
12
|
+
function "<name>" {
|
|
13
|
+
input { ... }
|
|
14
|
+
stack { ... }
|
|
15
|
+
response = $result
|
|
16
|
+
|
|
17
|
+
test "<test name>" {
|
|
18
|
+
input = { key: value }
|
|
19
|
+
expect.<assertion> ($response) { value = expected }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Assertions
|
|
25
|
+
| Assertion | Purpose |
|
|
26
|
+
|-----------|---------|
|
|
27
|
+
| `expect.to_equal` | Exact match |
|
|
28
|
+
| `expect.to_not_equal` | Not equal |
|
|
29
|
+
| `expect.to_be_true` | Is true |
|
|
30
|
+
| `expect.to_be_false` | Is false |
|
|
31
|
+
| `expect.to_be_null` | Is null |
|
|
32
|
+
| `expect.to_contain` | Array contains |
|
|
33
|
+
| `expect.to_match` | Regex match |
|
|
34
|
+
| `expect.to_throw` | Throws error |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Basic Test
|
|
39
|
+
|
|
40
|
+
```xs
|
|
41
|
+
function "add" {
|
|
42
|
+
input {
|
|
43
|
+
int a
|
|
44
|
+
int b
|
|
45
|
+
}
|
|
46
|
+
stack {
|
|
47
|
+
var $sum { value = $input.a + $input.b }
|
|
48
|
+
}
|
|
49
|
+
response = $sum
|
|
50
|
+
|
|
51
|
+
test "adds two numbers" {
|
|
52
|
+
input = { a: 5, b: 3 }
|
|
53
|
+
expect.to_equal ($response) { value = 8 }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test "handles negative numbers" {
|
|
57
|
+
input = { a: -5, b: 3 }
|
|
58
|
+
expect.to_equal ($response) { value = -2 }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Assertions
|
|
66
|
+
|
|
67
|
+
### Value Assertions
|
|
68
|
+
|
|
69
|
+
```xs
|
|
70
|
+
# Equality
|
|
71
|
+
expect.to_equal ($response.status) { value = "active" }
|
|
72
|
+
expect.to_not_equal ($response.status) { value = "deleted" }
|
|
73
|
+
|
|
74
|
+
# Boolean
|
|
75
|
+
expect.to_be_true ($response.is_active)
|
|
76
|
+
expect.to_be_false ($response.is_deleted)
|
|
77
|
+
|
|
78
|
+
# Null
|
|
79
|
+
expect.to_be_null ($response.deleted_at)
|
|
80
|
+
expect.to_not_be_null ($response.created_at)
|
|
81
|
+
|
|
82
|
+
# Defined
|
|
83
|
+
expect.to_be_defined ($response.id)
|
|
84
|
+
expect.to_not_be_defined ($response.optional_field)
|
|
85
|
+
|
|
86
|
+
# Empty
|
|
87
|
+
expect.to_be_empty ($response.errors)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Comparison Assertions
|
|
91
|
+
|
|
92
|
+
```xs
|
|
93
|
+
# Numeric comparisons
|
|
94
|
+
expect.to_be_greater_than ($response.total) { value = 100 }
|
|
95
|
+
expect.to_be_less_than ($response.stock) { value = 10 }
|
|
96
|
+
|
|
97
|
+
# Range
|
|
98
|
+
expect.to_be_within ($response.temperature) {
|
|
99
|
+
min = 20
|
|
100
|
+
max = 30
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### String Assertions
|
|
105
|
+
|
|
106
|
+
```xs
|
|
107
|
+
# Starts/ends with
|
|
108
|
+
expect.to_start_with ($response.name) { value = "John" }
|
|
109
|
+
expect.to_end_with ($response.file) { value = ".pdf" }
|
|
110
|
+
|
|
111
|
+
# Contains
|
|
112
|
+
expect.to_contain ($response.tags) { value = "featured" }
|
|
113
|
+
|
|
114
|
+
# Regex match
|
|
115
|
+
expect.to_match ($response.phone) { value = "^\\+1\\d{10}$" }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Time Assertions
|
|
119
|
+
|
|
120
|
+
```xs
|
|
121
|
+
expect.to_be_in_the_past ($response.created_at)
|
|
122
|
+
expect.to_be_in_the_future ($response.expires_at)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Error Assertions
|
|
126
|
+
|
|
127
|
+
```xs
|
|
128
|
+
# Expects any error
|
|
129
|
+
test "throws on invalid input" {
|
|
130
|
+
input = { amount: -1 }
|
|
131
|
+
expect.to_throw
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Expects specific error
|
|
135
|
+
test "throws validation error" {
|
|
136
|
+
input = { amount: -1 }
|
|
137
|
+
expect.to_throw { value = "InvalidInputError" }
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Mocking
|
|
144
|
+
|
|
145
|
+
Mock external calls to isolate tests:
|
|
146
|
+
|
|
147
|
+
```xs
|
|
148
|
+
function "get_weather" {
|
|
149
|
+
input { text city }
|
|
150
|
+
stack {
|
|
151
|
+
api.request {
|
|
152
|
+
url = "https://api.weather.com/current"
|
|
153
|
+
params = { city: $input.city }
|
|
154
|
+
mock = {
|
|
155
|
+
"returns sunny for Paris": { response: { weather: "sunny", temp: 22 } },
|
|
156
|
+
"returns rainy for London": { response: { weather: "rainy", temp: 15 } }
|
|
157
|
+
}
|
|
158
|
+
} as $weather
|
|
159
|
+
}
|
|
160
|
+
response = $weather.response
|
|
161
|
+
|
|
162
|
+
test "returns sunny for Paris" {
|
|
163
|
+
input = { city: "Paris" }
|
|
164
|
+
expect.to_equal ($response.weather) { value = "sunny" }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
test "returns rainy for London" {
|
|
168
|
+
input = { city: "London" }
|
|
169
|
+
expect.to_equal ($response.weather) { value = "rainy" }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Mock Matching
|
|
175
|
+
|
|
176
|
+
Mocks activate when their key matches the test name:
|
|
177
|
+
|
|
178
|
+
```xs
|
|
179
|
+
stack {
|
|
180
|
+
api.request {
|
|
181
|
+
url = "https://api.example.com/data"
|
|
182
|
+
mock = {
|
|
183
|
+
"test name here": { response: { ... } },
|
|
184
|
+
"another test": { response: { ... } }
|
|
185
|
+
}
|
|
186
|
+
} as $result
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Mocking Variables
|
|
191
|
+
|
|
192
|
+
```xs
|
|
193
|
+
stack {
|
|
194
|
+
var $message {
|
|
195
|
+
value = "Hello " ~ $input.name
|
|
196
|
+
mock = {
|
|
197
|
+
"custom greeting": "Custom greeting!"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Testing API Endpoints
|
|
206
|
+
|
|
207
|
+
```xs
|
|
208
|
+
query "products/{id}" verb=GET {
|
|
209
|
+
input {
|
|
210
|
+
int id { table = "product" }
|
|
211
|
+
}
|
|
212
|
+
stack {
|
|
213
|
+
db.get "product" {
|
|
214
|
+
field_name = "id"
|
|
215
|
+
field_value = $input.id
|
|
216
|
+
mock = {
|
|
217
|
+
"returns product": { id: 1, name: "Widget", price: 29.99 },
|
|
218
|
+
"handles not found": null
|
|
219
|
+
}
|
|
220
|
+
} as $product
|
|
221
|
+
}
|
|
222
|
+
response = $product
|
|
223
|
+
|
|
224
|
+
test "returns product" {
|
|
225
|
+
input = { id: 1 }
|
|
226
|
+
expect.to_equal ($response.name) { value = "Widget" }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
test "handles not found" {
|
|
230
|
+
input = { id: 999 }
|
|
231
|
+
expect.to_be_null ($response)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Testing Error Cases
|
|
239
|
+
|
|
240
|
+
```xs
|
|
241
|
+
function "validate_age" {
|
|
242
|
+
input { int age }
|
|
243
|
+
stack {
|
|
244
|
+
precondition ($input.age >= 0) {
|
|
245
|
+
error_type = "inputerror"
|
|
246
|
+
error = "Age cannot be negative"
|
|
247
|
+
}
|
|
248
|
+
precondition ($input.age <= 150) {
|
|
249
|
+
error_type = "inputerror"
|
|
250
|
+
error = "Age is unrealistic"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
response = { valid: true }
|
|
254
|
+
|
|
255
|
+
test "accepts valid age" {
|
|
256
|
+
input = { age: 25 }
|
|
257
|
+
expect.to_equal ($response.valid) { value = true }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
test "rejects negative age" {
|
|
261
|
+
input = { age: -5 }
|
|
262
|
+
expect.to_throw { value = "Age cannot be negative" }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
test "rejects unrealistic age" {
|
|
266
|
+
input = { age: 200 }
|
|
267
|
+
expect.to_throw { value = "Age is unrealistic" }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Complete Example
|
|
275
|
+
|
|
276
|
+
```xs
|
|
277
|
+
function "calculate_discount" {
|
|
278
|
+
input {
|
|
279
|
+
decimal subtotal filters=min:0
|
|
280
|
+
enum customer_type { values = ["regular", "premium", "vip"] }
|
|
281
|
+
text coupon?
|
|
282
|
+
}
|
|
283
|
+
stack {
|
|
284
|
+
var $discount_rate { value = 0 }
|
|
285
|
+
|
|
286
|
+
switch ($input.customer_type) {
|
|
287
|
+
case ("premium") { var.update $discount_rate { value = 0.1 } } break
|
|
288
|
+
case ("vip") { var.update $discount_rate { value = 0.2 } } break
|
|
289
|
+
default { var.update $discount_rate { value = 0 } }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
conditional {
|
|
293
|
+
if ($input.coupon == "SAVE10") {
|
|
294
|
+
math.add $discount_rate { value = 0.1 }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
var $discount { value = $input.subtotal * $discount_rate }
|
|
299
|
+
}
|
|
300
|
+
response = { discount: $discount, rate: $discount_rate }
|
|
301
|
+
|
|
302
|
+
test "no discount for regular customer" {
|
|
303
|
+
input = { subtotal: 100, customer_type: "regular" }
|
|
304
|
+
expect.to_equal ($response.discount) { value = 0 }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
test "10% discount for premium" {
|
|
308
|
+
input = { subtotal: 100, customer_type: "premium" }
|
|
309
|
+
expect.to_equal ($response.discount) { value = 10 }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
test "20% discount for VIP" {
|
|
313
|
+
input = { subtotal: 100, customer_type: "vip" }
|
|
314
|
+
expect.to_equal ($response.discount) { value = 20 }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
test "coupon stacks with VIP discount" {
|
|
318
|
+
input = { subtotal: 100, customer_type: "vip", coupon: "SAVE10" }
|
|
319
|
+
expect.to_equal ($response.rate) { value = 0.3 }
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Best Practices
|
|
327
|
+
|
|
328
|
+
1. **Test happy paths** - Verify expected behavior works
|
|
329
|
+
2. **Test edge cases** - Empty arrays, null values, boundaries
|
|
330
|
+
3. **Test error cases** - Invalid inputs, missing data
|
|
331
|
+
4. **Use mocks** - Isolate from external dependencies
|
|
332
|
+
5. **Descriptive test names** - Explain what's being tested
|
|
333
|
+
6. **One assertion focus** - Each test verifies one behavior
|
|
334
|
+
7. **Keep tests independent** - No shared state between tests
|
|
335
|
+
8. **Test at boundaries** - Min/max values, length limits
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "tools/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Tools
|
|
6
|
+
|
|
7
|
+
Functions that AI agents and MCP servers can execute.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
```xs
|
|
12
|
+
tool "<name>" {
|
|
13
|
+
description = "Internal documentation"
|
|
14
|
+
instructions = "How the AI should use this tool"
|
|
15
|
+
input { ... }
|
|
16
|
+
stack { ... }
|
|
17
|
+
response = $result
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Basic Structure
|
|
24
|
+
|
|
25
|
+
```xs
|
|
26
|
+
tool "get_user_by_email" {
|
|
27
|
+
description = "Look up user by email address"
|
|
28
|
+
instructions = "Use this to find user details when you have their email."
|
|
29
|
+
|
|
30
|
+
input {
|
|
31
|
+
email email filters=lower {
|
|
32
|
+
description = "The user's email address"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
stack {
|
|
37
|
+
db.get "user" {
|
|
38
|
+
field_name = "email"
|
|
39
|
+
field_value = $input.email
|
|
40
|
+
} as $user
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
response = $user
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Key Fields
|
|
50
|
+
|
|
51
|
+
| Field | Purpose | Visibility |
|
|
52
|
+
|-------|---------|------------|
|
|
53
|
+
| `description` | Internal documentation | Not sent to AI |
|
|
54
|
+
| `instructions` | How AI should use tool | Sent to AI |
|
|
55
|
+
| `input` | Parameters with descriptions | Sent to AI |
|
|
56
|
+
| `stack` | Execution logic | Not sent to AI |
|
|
57
|
+
| `response` | Return value | Sent to AI |
|
|
58
|
+
|
|
59
|
+
**Important:** `instructions` and input `description` fields are sent to the AI. Write them clearly.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Input Block
|
|
64
|
+
|
|
65
|
+
Same as functions, but descriptions are especially important:
|
|
66
|
+
|
|
67
|
+
```xs
|
|
68
|
+
input {
|
|
69
|
+
int order_id {
|
|
70
|
+
description = "The unique order ID to look up"
|
|
71
|
+
}
|
|
72
|
+
enum status {
|
|
73
|
+
description = "New status to set"
|
|
74
|
+
values = ["pending", "processing", "shipped", "delivered"]
|
|
75
|
+
}
|
|
76
|
+
text reason? {
|
|
77
|
+
description = "Optional reason for the status change"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Tool-Specific Statements
|
|
85
|
+
|
|
86
|
+
### api.call
|
|
87
|
+
Call an API endpoint:
|
|
88
|
+
|
|
89
|
+
```xs
|
|
90
|
+
stack {
|
|
91
|
+
api.call "orders/get" verb=GET {
|
|
92
|
+
api_group = "orders"
|
|
93
|
+
input = { order_id: $input.order_id }
|
|
94
|
+
} as $order
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### task.call
|
|
99
|
+
Trigger a background task:
|
|
100
|
+
|
|
101
|
+
```xs
|
|
102
|
+
stack {
|
|
103
|
+
task.call "send_notification" as $result
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### tool.call
|
|
108
|
+
Call another tool:
|
|
109
|
+
|
|
110
|
+
```xs
|
|
111
|
+
stack {
|
|
112
|
+
tool.call "get_user_by_id" {
|
|
113
|
+
input = { user_id: $input.user_id }
|
|
114
|
+
} as $user
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Common Patterns
|
|
121
|
+
|
|
122
|
+
### Database Lookup
|
|
123
|
+
```xs
|
|
124
|
+
tool "get_order_status" {
|
|
125
|
+
instructions = "Check the current status of an order by its ID."
|
|
126
|
+
input {
|
|
127
|
+
int order_id { description = "Order ID to check" }
|
|
128
|
+
}
|
|
129
|
+
stack {
|
|
130
|
+
db.get "order" {
|
|
131
|
+
field_name = "id"
|
|
132
|
+
field_value = $input.order_id
|
|
133
|
+
} as $order
|
|
134
|
+
}
|
|
135
|
+
response = {
|
|
136
|
+
order_id: $order.id,
|
|
137
|
+
status: $order.status,
|
|
138
|
+
updated_at: $order.updated_at
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Database Update
|
|
144
|
+
```xs
|
|
145
|
+
tool "update_order_status" {
|
|
146
|
+
instructions = "Update an order's status. Use when customer requests changes."
|
|
147
|
+
input {
|
|
148
|
+
int order_id { description = "Order ID to update" }
|
|
149
|
+
enum status {
|
|
150
|
+
description = "New status"
|
|
151
|
+
values = ["pending", "processing", "shipped", "cancelled"]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
stack {
|
|
155
|
+
db.edit "order" {
|
|
156
|
+
field_name = "id"
|
|
157
|
+
field_value = $input.order_id
|
|
158
|
+
data = { status: $input.status, updated_at: now }
|
|
159
|
+
} as $order
|
|
160
|
+
}
|
|
161
|
+
response = $order
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### External API Call
|
|
166
|
+
```xs
|
|
167
|
+
tool "get_weather" {
|
|
168
|
+
instructions = "Get current weather for a city."
|
|
169
|
+
input {
|
|
170
|
+
text city filters=trim { description = "City name" }
|
|
171
|
+
}
|
|
172
|
+
stack {
|
|
173
|
+
api.request {
|
|
174
|
+
url = "https://api.weather.com/current"
|
|
175
|
+
method = "GET"
|
|
176
|
+
params = { q: $input.city, key: $env.WEATHER_API_KEY }
|
|
177
|
+
} as $weather
|
|
178
|
+
}
|
|
179
|
+
response = {
|
|
180
|
+
city: $input.city,
|
|
181
|
+
temperature: $weather.temp,
|
|
182
|
+
conditions: $weather.conditions
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Search Tool
|
|
188
|
+
```xs
|
|
189
|
+
tool "search_products" {
|
|
190
|
+
instructions = "Search products by name or category."
|
|
191
|
+
input {
|
|
192
|
+
text query? { description = "Search term" }
|
|
193
|
+
text category? { description = "Category filter" }
|
|
194
|
+
int limit?=10 { description = "Max results (default 10)" }
|
|
195
|
+
}
|
|
196
|
+
stack {
|
|
197
|
+
db.query "product" {
|
|
198
|
+
where = $db.product.name includes? $input.query
|
|
199
|
+
&& $db.product.category ==? $input.category
|
|
200
|
+
&& $db.product.is_active == true
|
|
201
|
+
return = { type: "list", paging: { page: 1, per_page: $input.limit } }
|
|
202
|
+
} as $products
|
|
203
|
+
}
|
|
204
|
+
response = $products.items
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Create Record
|
|
209
|
+
```xs
|
|
210
|
+
tool "create_ticket" {
|
|
211
|
+
instructions = "Create a support ticket for the customer."
|
|
212
|
+
input {
|
|
213
|
+
text subject filters=trim { description = "Ticket subject" }
|
|
214
|
+
text description filters=trim { description = "Issue description" }
|
|
215
|
+
enum priority?="medium" {
|
|
216
|
+
description = "Ticket priority"
|
|
217
|
+
values = ["low", "medium", "high", "urgent"]
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
stack {
|
|
221
|
+
db.add "ticket" {
|
|
222
|
+
data = {
|
|
223
|
+
subject: $input.subject,
|
|
224
|
+
description: $input.description,
|
|
225
|
+
priority: $input.priority,
|
|
226
|
+
status: "open",
|
|
227
|
+
created_at: now
|
|
228
|
+
}
|
|
229
|
+
} as $ticket
|
|
230
|
+
}
|
|
231
|
+
response = { ticket_id: $ticket.id, message: "Ticket created" }
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Composed Tool
|
|
236
|
+
```xs
|
|
237
|
+
tool "get_order_with_items" {
|
|
238
|
+
instructions = "Get complete order details including all items."
|
|
239
|
+
input {
|
|
240
|
+
int order_id { description = "Order ID" }
|
|
241
|
+
}
|
|
242
|
+
stack {
|
|
243
|
+
tool.call "get_order_status" {
|
|
244
|
+
input = { order_id: $input.order_id }
|
|
245
|
+
} as $order
|
|
246
|
+
|
|
247
|
+
db.query "order_item" {
|
|
248
|
+
where = $db.order_item.order_id == $input.order_id
|
|
249
|
+
} as $items
|
|
250
|
+
}
|
|
251
|
+
response = {
|
|
252
|
+
order: $order,
|
|
253
|
+
items: $items
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Error Handling
|
|
261
|
+
|
|
262
|
+
```xs
|
|
263
|
+
tool "cancel_order" {
|
|
264
|
+
instructions = "Cancel an order. Only works for pending orders."
|
|
265
|
+
input {
|
|
266
|
+
int order_id { description = "Order to cancel" }
|
|
267
|
+
}
|
|
268
|
+
stack {
|
|
269
|
+
db.get "order" {
|
|
270
|
+
field_name = "id"
|
|
271
|
+
field_value = $input.order_id
|
|
272
|
+
} as $order
|
|
273
|
+
|
|
274
|
+
precondition ($order != null) {
|
|
275
|
+
error_type = "notfound"
|
|
276
|
+
error = "Order not found"
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
precondition ($order.status == "pending") {
|
|
280
|
+
error_type = "standard"
|
|
281
|
+
error = "Only pending orders can be cancelled"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
db.edit "order" {
|
|
285
|
+
field_name = "id"
|
|
286
|
+
field_value = $input.order_id
|
|
287
|
+
data = { status: "cancelled" }
|
|
288
|
+
} as $updated
|
|
289
|
+
}
|
|
290
|
+
response = { success: true, order_id: $input.order_id }
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Best Practices
|
|
297
|
+
|
|
298
|
+
1. **Write clear instructions** - This is what the AI reads to understand the tool
|
|
299
|
+
2. **Describe all inputs** - Help AI construct valid requests
|
|
300
|
+
3. **Use enums for fixed options** - Reduces AI errors
|
|
301
|
+
4. **Keep tools focused** - One task per tool
|
|
302
|
+
5. **Handle errors gracefully** - Return clear error messages
|
|
303
|
+
6. **Validate inputs** - Use filters and preconditions
|
|
304
|
+
7. **Limit response size** - Don't return huge datasets
|
|
305
|
+
8. **Use composition** - Call other tools for complex operations
|