@xano/xanoscript-language-server 12.20.0 → 12.21.0
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 +85 -146
- package/embedded/README.md +1 -2
- package/lexer/mcp.js +2 -3
- package/lexer/realtime_server_trigger.js +1 -1
- package/onHover/functions.md +4 -1
- package/package.json +15 -5
- package/parser/clauses/historyClause.js +1 -2
- package/parser/clauses/mcpMetadataClauses.js +4 -6
- package/parser/definitions/dbLinkColumnDefinition.js +1 -1
- package/parser/functions/controls/throwFn.js +28 -2
- package/parser/functions/mcp/mcpElicitFn.js +1 -1
- package/parser/functions/mcp/mcpOauthFn.js +2 -4
- package/parser/functions/mcp/mcpProgressFn.js +2 -2
- package/parser/functions/realtime/realtimePublishFn.js +2 -2
- package/parser/mcp_primitive_parser.js +4 -4
- package/parser/mcp_server_parser.js +1 -2
- package/parser/message_parser.js +3 -3
- package/parser/microservice_parser.js +1 -2
- package/parser/parser.js +1 -1
- package/parser/policy/catalogue.js +2 -2
- package/parser/policy/policyLiteral.js +1 -1
- package/parser/policy/validatePolicyRule.js +10 -11
- package/parser/policy_parser.js +3 -3
- package/parser/realtime_channel_parser.js +1 -1
package/README.md
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
```
|
|
2
|
-
██╗ ██╗███████╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ██████╗ ███████╗
|
|
3
|
-
╚██╗██╔╝██╔════╝ ██║ ██╔══██╗████╗ ██║██╔════╝ ██║ ██║██╔══██╗██╔════╝ ██╔════╝
|
|
4
|
-
╚███╔╝ ███████╗ ██║ ███████║██╔██╗ ██║██║ ███╗██║ ██║███████║██║ ███╗█████╗
|
|
5
|
-
██╔██╗ ╚════██║ ██║ ██╔══██║██║╚██╗██║██║ ██║██║ ██║██╔══██║██║ ██║██╔══╝
|
|
6
|
-
██╔╝ ██╗███████║ ███████╗██║ ██║██║ ╚████║╚██████╔╝╚██████╔╝██║ ██║╚██████╔╝███████╗
|
|
7
|
-
╚═╝ ╚═╝╚══════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
|
|
8
|
-
|
|
9
|
-
███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗
|
|
10
|
-
██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗
|
|
11
|
-
███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝
|
|
12
|
-
╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
|
13
|
-
███████║███████╗██║ ██║ ╚████╔╝ ███████╗██║ ██║
|
|
14
|
-
╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
|
|
15
|
-
```
|
|
16
|
-
|
|
17
1
|
# XanoScript Language Server
|
|
18
2
|
|
|
19
|
-
|
|
3
|
+
The [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) implementation for XanoScript, the language that describes a [Xano](https://www.xano.com) backend: its APIs, functions, tables, tasks, agents, MCP servers and policies.
|
|
20
4
|
|
|
21
|
-
|
|
5
|
+
Xano is one platform to build, run and govern AI-built software. This server brings the platform's rules into the editor, so you and your coding agent see the problems a push would refuse while you write the code.
|
|
6
|
+
|
|
7
|
+
Built on [Chevrotain](https://chevrotain.io/) and maintained by the Xano team.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Diagnostics** - Errors and warnings as you type, including cross-file references, variable usage and workspace policy checks
|
|
12
|
+
- **Completion** - Context-aware suggestions, triggered by `.`, `:`, `$` and `|`
|
|
13
|
+
- **Hover** - Documentation for statements, filters, input variables and policy checks
|
|
14
|
+
- **Go to definition** - Jump to a referenced function, task, query or table, or to where a variable is set
|
|
15
|
+
- **Semantic highlighting** - Token-based highlighting for every XanoScript construct
|
|
22
16
|
|
|
23
17
|
## Installation
|
|
24
18
|
|
|
@@ -26,155 +20,100 @@ Maintained by the [Xano](https://xano.com) team.
|
|
|
26
20
|
npm install @xano/xanoscript-language-server
|
|
27
21
|
```
|
|
28
22
|
|
|
29
|
-
|
|
23
|
+
Requires Node.js 20 or later.
|
|
30
24
|
|
|
31
|
-
##
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
The package ships two entry points:
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
| Entry point | Runtime | Transport |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `server.js` | Node.js | stdio / IPC, via `vscode-languageserver/node` |
|
|
32
|
+
| `language-server-worker.js` | Browser (Web Worker) | `postMessage`, via `vscode-languageserver/browser` |
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
- **Syntax Highlighting** - Semantic token-based highlighting for XanoScript constructs
|
|
37
|
-
- **Hover Information** - Documentation and type information on hover
|
|
38
|
-
- **Diagnostics** - Real-time error detection and reporting
|
|
34
|
+
Point any LSP client at `server.js` for `.xs` files. For example, with VS Code's `vscode-languageclient`:
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
```js
|
|
37
|
+
const serverModule = require.resolve("@xano/xanoscript-language-server/server.js");
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
const client = new LanguageClient(
|
|
40
|
+
"xanoscript",
|
|
41
|
+
"XanoScript",
|
|
42
|
+
{
|
|
43
|
+
run: { module: serverModule, transport: TransportKind.ipc },
|
|
44
|
+
debug: { module: serverModule, transport: TransportKind.ipc },
|
|
45
|
+
},
|
|
46
|
+
{ documentSelector: [{ scheme: "file", language: "xanoscript" }] }
|
|
47
|
+
);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Policy checks
|
|
51
|
+
|
|
52
|
+
XanoScript `policy` files are checked against Xano's policy check catalogue. The server ships a snapshot of it (`parser/policy/catalogueSnapshot.js`), so what it reports is a warning: your instance may be newer than the snapshot. A host that can fetch the live catalogue can load it with `setPolicyCatalogue()` from `parser/policy/catalogue.js`.
|
|
53
|
+
|
|
54
|
+
## Supported object types
|
|
43
55
|
|
|
44
56
|
| Type | Description |
|
|
45
57
|
|------|-------------|
|
|
46
|
-
| `query` | API endpoints with HTTP verbs, input validation
|
|
47
|
-
| `function` | Reusable logic blocks with testing capabilities |
|
|
48
|
-
| `task` | Scheduled operations with cron-like triggers |
|
|
58
|
+
| `query` | API endpoints with HTTP verbs, input validation and responses |
|
|
49
59
|
| `api_group` | Collections of related API endpoints |
|
|
60
|
+
| `function` | Reusable logic blocks with tests |
|
|
61
|
+
| `task` | Scheduled operations with cron-like triggers |
|
|
50
62
|
| `table` | Database schema definitions with column types |
|
|
51
|
-
| `table_trigger` | Database table
|
|
52
|
-
| `workflow_test` |
|
|
53
|
-
| `addon` | Add-on definitions |
|
|
54
|
-
| `agent` | AI agent definitions |
|
|
55
|
-
| `agent_trigger` | Agent trigger handlers |
|
|
56
|
-
| `branch` | Branch logic |
|
|
57
|
-
| `mcp_server` | MCP server definitions |
|
|
58
|
-
| `mcp_server_trigger` | MCP server trigger handlers |
|
|
63
|
+
| `table_trigger` | Database table triggers |
|
|
64
|
+
| `workflow_test` | Workflow test definitions |
|
|
59
65
|
| `middleware` | Middleware definitions |
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
66
|
+
| `addon` | Add-on definitions |
|
|
67
|
+
| `branch` | Branch definitions |
|
|
68
|
+
| `workspace` / `workspace_trigger` | Workspace definitions and triggers |
|
|
69
|
+
| `error_trigger` | Error trigger handlers |
|
|
70
|
+
| `agent` / `agent_trigger` | AI agent definitions and triggers |
|
|
71
|
+
| `tool` | Tool definitions, including MCP tool metadata |
|
|
72
|
+
| `mcp_server` / `mcp_server_trigger` | MCP server definitions and connection triggers |
|
|
73
|
+
| `prompt` / `resource` | MCP prompts and resources |
|
|
74
|
+
| `policy` | Workspace policies and their rules |
|
|
75
|
+
| `microservice` | Container and Helm microservice deployments |
|
|
76
|
+
| `realtime_channel` / `realtime_trigger` | Realtime v1 channels and triggers |
|
|
77
|
+
| `realtime_server` / `realtime_server_trigger` | Realtime v2 servers and connection triggers |
|
|
78
|
+
| `channel` / `channel_trigger` | Realtime v2 channels and join/leave triggers |
|
|
64
79
|
| `message` | Realtime v2 channel message handlers |
|
|
65
|
-
| `
|
|
66
|
-
| `realtime_server_trigger` | Realtime v2 connection lifecycle triggers (connect/disconnect) |
|
|
67
|
-
| `tool` | Tool definitions |
|
|
68
|
-
| `workspace` | Workspace definitions |
|
|
69
|
-
| `workspace_trigger` | Workspace trigger handlers |
|
|
70
|
-
|
|
71
|
-
## Built-in Function Domains
|
|
72
|
-
|
|
73
|
-
XanoScript includes functions organized by domain:
|
|
74
|
-
|
|
75
|
-
- **ai** - AI and machine learning operations
|
|
76
|
-
- **api** - External API calls and HTTP requests
|
|
77
|
-
- **array** - Array manipulation and iteration
|
|
78
|
-
- **cloud** - Cloud service integrations
|
|
79
|
-
- **controls** - Flow control (conditionals, loops, etc.)
|
|
80
|
-
- **db** - Database queries and operations
|
|
81
|
-
- **debug** - Debugging and logging utilities
|
|
82
|
-
- **expect** - Testing assertions
|
|
83
|
-
- **math** - Mathematical operations
|
|
84
|
-
- **object** - Object manipulation
|
|
85
|
-
- **redis** - Redis cache operations
|
|
86
|
-
- **schema** - Schema operations
|
|
87
|
-
- **security** - Authentication and security functions
|
|
88
|
-
- **storage** - File storage operations
|
|
89
|
-
- **stream** - Streaming data operations
|
|
90
|
-
- **text** - String manipulation
|
|
91
|
-
- **util** - General utilities
|
|
92
|
-
- **var** - Variable operations
|
|
93
|
-
- **zip** - Compression utilities
|
|
94
|
-
|
|
95
|
-
## Project Structure
|
|
80
|
+
| `run` | Run job and service definitions |
|
|
96
81
|
|
|
97
|
-
|
|
98
|
-
├── server.js # LSP server entry point
|
|
99
|
-
├── lexer/ # Lexical analysis
|
|
100
|
-
│ ├── lexer.js # Chevrotain lexer implementation
|
|
101
|
-
│ ├── tokens.js # Main token registry
|
|
102
|
-
│ └── [domain].js # Domain-specific tokens (api, db, cloud, etc.)
|
|
103
|
-
├── parser/ # Syntax parsing
|
|
104
|
-
│ ├── base_parser.js # Core XanoBaseParser
|
|
105
|
-
│ ├── [type]_parser.js # Object type parsers
|
|
106
|
-
│ ├── attributes/ # Field attributes (description, disabled, sensitive)
|
|
107
|
-
│ ├── clauses/ # Language blocks (stack, input, response, etc.)
|
|
108
|
-
│ ├── definitions/ # Type and column definitions
|
|
109
|
-
│ ├── functions/ # Built-in functions by domain
|
|
110
|
-
│ └── generic/ # Reusable parsing components
|
|
111
|
-
├── onCompletion/ # Code completion logic
|
|
112
|
-
├── onDidChangeContent/ # Diagnostics and error reporting
|
|
113
|
-
├── onHover/ # Hover documentation
|
|
114
|
-
└── onSemanticCheck/ # Semantic token highlighting
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Development
|
|
118
|
-
|
|
119
|
-
```sh
|
|
120
|
-
# Install dependencies
|
|
121
|
-
npm install
|
|
122
|
-
|
|
123
|
-
# Run all tests (~1800 tests)
|
|
124
|
-
npm test
|
|
82
|
+
## Built-in function domains
|
|
125
83
|
|
|
126
|
-
|
|
127
|
-
npm test -- -g "test name"
|
|
84
|
+
`ai`, `api`, `array`, `cloud`, `controls`, `datadog`, `db`, `debug`, `expect`, `math`, `mcp`, `object`, `realtime`, `redis`, `schema`, `security`, `storage`, `stream`, `text`, `util`, `var`, `zip`
|
|
128
85
|
|
|
129
|
-
|
|
130
|
-
npm run lint
|
|
86
|
+
## Project structure
|
|
131
87
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
#
|
|
136
|
-
|
|
88
|
+
```
|
|
89
|
+
├── server.js # Node LSP entry point
|
|
90
|
+
├── language-server-worker.js # Browser (Web Worker) entry point
|
|
91
|
+
├── lexer/ # Token definitions
|
|
92
|
+
├── parser/ # Grammar rules
|
|
93
|
+
│ ├── base_parser.js # Core XanoBaseParser
|
|
94
|
+
│ ├── [type]_parser.js # One parser per object type
|
|
95
|
+
│ ├── attributes/ # Field attributes (description, disabled, sensitive)
|
|
96
|
+
│ ├── clauses/ # Blocks (stack, input, response, ...)
|
|
97
|
+
│ ├── definitions/ # Type and column definitions
|
|
98
|
+
│ ├── functions/ # Built-in statements by domain
|
|
99
|
+
│ ├── generic/ # Shared parsing components
|
|
100
|
+
│ └── policy/ # Policy check catalogue
|
|
101
|
+
├── onCompletion/ # Completion
|
|
102
|
+
├── onDefinition/ # Go to definition
|
|
103
|
+
├── onDidChangeContent/ # Diagnostics
|
|
104
|
+
├── onHover/ # Hover documentation
|
|
105
|
+
├── onSemanticCheck/ # Semantic tokens
|
|
106
|
+
└── workspace/ # Cross-file index and validation
|
|
137
107
|
```
|
|
138
108
|
|
|
139
|
-
##
|
|
140
|
-
|
|
141
|
-
### New Object Type
|
|
142
|
-
|
|
143
|
-
1. Create a new parser in `parser/` (e.g., `my_object_parser.js`)
|
|
144
|
-
2. Follow existing parser patterns (see `query_parser.js`, `function_parser.js`)
|
|
145
|
-
3. Register it in `server.js`
|
|
146
|
-
|
|
147
|
-
### New Keyword/Function
|
|
148
|
-
|
|
149
|
-
We have a hierarchy for the different statements XanoScript uses:
|
|
150
|
-
|
|
151
|
-
1. **Parser** - Top level (Query parser, Function parser, etc.)
|
|
152
|
-
2. **Clause** - Main blocks in a given object type (stack, response, input, etc.)
|
|
153
|
-
3. **Functions** - Statements like `debug.log` or `db.query`
|
|
154
|
-
4. **Attributes/Definitions** - Components used to define functions
|
|
155
|
-
5. **Generic** - Shared language toolset
|
|
156
|
-
|
|
157
|
-
To add a new function:
|
|
158
|
-
|
|
159
|
-
1. Add token definition in appropriate `lexer/` file
|
|
160
|
-
2. Create function implementation in `parser/functions/[domain]/`
|
|
161
|
-
3. Register in parent clause or parser
|
|
162
|
-
4. Add comprehensive tests in corresponding `.spec.js` file
|
|
163
|
-
|
|
164
|
-
## Testing
|
|
165
|
-
|
|
166
|
-
The language server is heavily tested and should remain that way. Tests should be:
|
|
167
|
-
|
|
168
|
-
- Easy to copy, paste, and extend
|
|
169
|
-
- Straightforward and easy to read
|
|
170
|
-
- Avoid dynamic generation (no loops)
|
|
109
|
+
## Contributing
|
|
171
110
|
|
|
172
|
-
|
|
111
|
+
This repository receives one commit per release. Development happens elsewhere, so pull requests are turned off here. Found a bug? [Open an issue](https://github.com/xano-inc/xanoscript-language-server/issues/new?template=bug_report.yml). [CONTRIBUTING.md](CONTRIBUTING.md) says what makes a report quick to fix.
|
|
173
112
|
|
|
174
|
-
##
|
|
113
|
+
## Security
|
|
175
114
|
|
|
176
|
-
|
|
115
|
+
To report a vulnerability, see [SECURITY.md](SECURITY.md). Please don't open a public issue.
|
|
177
116
|
|
|
178
117
|
## License
|
|
179
118
|
|
|
180
|
-
MIT
|
|
119
|
+
[MIT](LICENSE)
|
package/embedded/README.md
CHANGED
|
@@ -81,7 +81,7 @@ This works in any context (not just `api.lambda`), making it flexible for future
|
|
|
81
81
|
## Client-Side vs Server-Side
|
|
82
82
|
|
|
83
83
|
- **Server-side** (this folder): JavaScript implementation used by language server
|
|
84
|
-
- **Client-side
|
|
84
|
+
- **Client-side**: the editor extension creates the virtual documents with the same extraction logic
|
|
85
85
|
- Both implement the same extraction logic but in their respective environments
|
|
86
86
|
|
|
87
87
|
## Testing
|
|
@@ -89,7 +89,6 @@ This works in any context (not just `api.lambda`), making it flexible for future
|
|
|
89
89
|
All modules are ES6 modules and can be tested independently:
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
cd language-server
|
|
93
92
|
node -e "import('./embedded/embeddedContent.js').then(m => console.log(m.extractEmbeddedJS('code = \"\"\"\\nconsole.log()\\n\"\"\"')))"
|
|
94
93
|
```
|
|
95
94
|
|
package/lexer/mcp.js
CHANGED
|
@@ -4,9 +4,8 @@ import { createTokenByName } from "./utils.js";
|
|
|
4
4
|
// MCP 2026-07-28 authoring keywords: the `prompt` and `resource` object kinds,
|
|
5
5
|
// the tool/prompt/resource metadata keys, and the `mcp.elicit` / `mcp.progress`
|
|
6
6
|
// statements, plus the per-server `oauth` block and the `mcp.oauth.complete` /
|
|
7
|
-
// `mcp.oauth.request` / `mcp.oauth.revoke` statements. Grounded against
|
|
8
|
-
//
|
|
9
|
-
// `script/kind/schema/statement/mcp.{elicit,progress}.yaml`).
|
|
7
|
+
// `mcp.oauth.request` / `mcp.oauth.revoke` statements. Grounded against the Xano
|
|
8
|
+
// backend's XanoScript schema.
|
|
10
9
|
//
|
|
11
10
|
// Ordering note: a keyword with `longer_alt: Identifier` loses to an EARLIER
|
|
12
11
|
// shorter token, so `prompts` must precede `prompt`, and `resource` is lexed
|
|
@@ -14,7 +14,7 @@ export const RealtimeServerTriggerToken = createTokenByName(
|
|
|
14
14
|
// longer_alt lists the trigger token so `realtime_server_trigger` wins over the
|
|
15
15
|
// shorter `realtime_server` prefix, AND Identifier so a name that merely EXTENDS
|
|
16
16
|
// the keyword (e.g. `realtime_server_0`, the default scaffold name) tokenizes as
|
|
17
|
-
// one identifier instead of keyword + leftover `_0
|
|
17
|
+
// one identifier instead of keyword + leftover `_0`. longer_alt is not
|
|
18
18
|
// transitive, so Identifier must be listed here directly — matching the
|
|
19
19
|
// [ServerDetailsToken, Identifier] precedent in channel.js.
|
|
20
20
|
export const RealtimeServerToken = createTokenByName("realtime_server", {
|
package/onHover/functions.md
CHANGED
|
@@ -1985,10 +1985,13 @@ Removes characters (default is whitespace, or as specified by `value`) from both
|
|
|
1985
1985
|
throw {
|
|
1986
1986
|
name = "ValidationError"
|
|
1987
1987
|
value = "Invalid user input provided"
|
|
1988
|
+
status = 400
|
|
1988
1989
|
}
|
|
1989
1990
|
```
|
|
1990
1991
|
|
|
1991
|
-
Throws an error and halts the script’s execution immediately. The `name` parameter specifies the error type (e.g., `"ValidationError"`), and `value` provides a custom error message to describe the issue.
|
|
1992
|
+
Throws an error and halts the script’s execution immediately. The `name` parameter specifies the error type (e.g., `"ValidationError"`), and `value` provides a custom error message to describe the issue. A `try_catch` can catch it.
|
|
1993
|
+
|
|
1994
|
+
`status` (optional, a whole number from 100 to 599) is the HTTP status the request answers with when nothing catches the throw, including a throw inside a function run with `function.run`. Without it, an uncaught throw answers 200. A caught throw never changes the status.
|
|
1992
1995
|
|
|
1993
1996
|
# try_catch
|
|
1994
1997
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xano/xanoscript-language-server",
|
|
3
|
-
"version": "12.
|
|
4
|
-
"description": "Language Server Protocol implementation for XanoScript",
|
|
3
|
+
"version": "12.21.0",
|
|
4
|
+
"description": "Language Server Protocol implementation for XanoScript, the language of Xano backends",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"xano",
|
|
7
|
+
"xanoscript",
|
|
8
|
+
"language-server",
|
|
9
|
+
"lsp",
|
|
10
|
+
"chevrotain"
|
|
11
|
+
],
|
|
5
12
|
"type": "module",
|
|
6
13
|
"main": "server.js",
|
|
7
14
|
"files": [
|
|
@@ -31,14 +38,17 @@
|
|
|
31
38
|
],
|
|
32
39
|
"repository": {
|
|
33
40
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/xano-
|
|
41
|
+
"url": "git+https://github.com/xano-inc/xanoscript-language-server.git"
|
|
35
42
|
},
|
|
36
43
|
"author": "Xano <support@xano.com>",
|
|
37
44
|
"license": "MIT",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
},
|
|
38
48
|
"bugs": {
|
|
39
|
-
"url": "https://github.com/xano-
|
|
49
|
+
"url": "https://github.com/xano-inc/xanoscript-language-server/issues"
|
|
40
50
|
},
|
|
41
|
-
"homepage": "https://github.com/xano-
|
|
51
|
+
"homepage": "https://github.com/xano-inc/xanoscript-language-server#readme",
|
|
42
52
|
"scripts": {
|
|
43
53
|
"test-one": "mocha",
|
|
44
54
|
"test": "mocha \"**/*.spec.js\"",
|
|
@@ -5,8 +5,7 @@ import { HistoryToken } from "../../lexer/tokens.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* Parses the history clause.
|
|
7
7
|
*
|
|
8
|
-
* `history` mirrors the backend serializer
|
|
9
|
-
* helper/script/transform via `Transform::onSchemaRequestHistory`): the value is
|
|
8
|
+
* `history` mirrors the backend serializer: the value is
|
|
10
9
|
* `false` (disabled), `"inherit"` (hand-authored sentinel), `"all"` (unbounded),
|
|
11
10
|
* or ANY non-negative integer message limit — not a fixed set of magic numbers.
|
|
12
11
|
* It is split across two OR branches because `schemaParseEnumFn` refuses an enum
|
|
@@ -7,10 +7,8 @@ import { AnnotationsToken, IconsToken, TitleToken } from "../../lexer/mcp.js";
|
|
|
7
7
|
import { MultiLineStringToken } from "../../lexer/multiline.js";
|
|
8
8
|
|
|
9
9
|
// Shared MCP metadata blocks for `tool`, `prompt` and `resource` (MCP spec
|
|
10
|
-
// 2026-07-28). The rules mirror
|
|
11
|
-
// reports what a push would refuse
|
|
12
|
-
// - `helper/mvp/mcpserver/ToolMetadata.php` (icons, tool annotations)
|
|
13
|
-
// - `helper/script/transform/Resource.php` (resource annotations)
|
|
10
|
+
// 2026-07-28). The rules mirror the Xano backend's decode checks (icons, tool annotations,
|
|
11
|
+
// resource annotations) so the editor reports what a push would refuse.
|
|
14
12
|
|
|
15
13
|
export const ICON_MIME_TYPES = ["image/png", "image/jpeg", "image/webp"];
|
|
16
14
|
export const ICON_URL_MAX_LENGTH = 2048;
|
|
@@ -19,7 +17,7 @@ export const RESOURCE_AUDIENCES = ["user", "assistant"];
|
|
|
19
17
|
|
|
20
18
|
const ICON_SCHEMA = {
|
|
21
19
|
src: "[string]",
|
|
22
|
-
// validated below:
|
|
20
|
+
// validated below: the backend compares it lowercased and allows ""
|
|
23
21
|
"mime_type?": "[string]",
|
|
24
22
|
"sizes?": ["[string]"],
|
|
25
23
|
"theme?": ["light", "dark"],
|
|
@@ -47,7 +45,7 @@ function stringValue(token) {
|
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
/**
|
|
50
|
-
* Why an icon `src` is refused, or null. Same rules as
|
|
48
|
+
* Why an icon `src` is refused, or null. Same rules as the platform:
|
|
51
49
|
* `https:` or a base64 `data:` URI of png/jpeg/webp. SVG and every other scheme
|
|
52
50
|
* (`http:`, `javascript:`, `file:`, ...) are refused.
|
|
53
51
|
* @param {string} src
|
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import { ThrowToken } from "../../../lexer/control.js";
|
|
2
|
+
import { IntegerLiteral } from "../../../lexer/literal.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `status` is the HTTP status an UNCAUGHT throw answers with
|
|
6
|
+
* (mvp:throw_error, `status?: static:int`).
|
|
7
|
+
* It is a static integer from 100 to 599, never an expression, so a variable
|
|
8
|
+
* or an out-of-range number is caught in the editor rather than at run time.
|
|
9
|
+
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
10
|
+
*/
|
|
11
|
+
function statusValue($) {
|
|
12
|
+
const token = $.CONSUME(IntegerLiteral, {
|
|
13
|
+
ERR_MSG: "status must be a whole number from 100 to 599",
|
|
14
|
+
});
|
|
15
|
+
const value = Number(token.image);
|
|
16
|
+
if (!Number.isInteger(value) || value < 100 || value > 599) {
|
|
17
|
+
$.addInvalidValueError(
|
|
18
|
+
token,
|
|
19
|
+
`Invalid status ${token.image}. Must be a whole number from 100 to 599`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
2
23
|
|
|
3
24
|
/**
|
|
4
25
|
* represent $var.x or $x, the only format accepting an assigment
|
|
@@ -6,14 +27,19 @@ import { ThrowToken } from "../../../lexer/control.js";
|
|
|
6
27
|
*/
|
|
7
28
|
export function throwFn($) {
|
|
8
29
|
const requiredAttrs = ["value", "name"];
|
|
9
|
-
const optionalAttrs = ["description", "disabled"];
|
|
30
|
+
const optionalAttrs = ["description", "disabled", "status"];
|
|
10
31
|
|
|
11
32
|
return () => {
|
|
12
33
|
$.sectionStack.push("throw");
|
|
13
34
|
const tokenFn = $.CONSUME(ThrowToken); // "throw"
|
|
14
35
|
// value is a required field
|
|
15
36
|
$.SUBRULE($.functionAttrReq, {
|
|
16
|
-
ARGS: [
|
|
37
|
+
ARGS: [
|
|
38
|
+
tokenFn,
|
|
39
|
+
requiredAttrs,
|
|
40
|
+
optionalAttrs,
|
|
41
|
+
{ types: { status: statusValue } },
|
|
42
|
+
],
|
|
17
43
|
});
|
|
18
44
|
|
|
19
45
|
$.sectionStack.pop();
|
|
@@ -13,7 +13,7 @@ import { checkElicit } from "./elicitLint.js";
|
|
|
13
13
|
// }
|
|
14
14
|
// } as $answer
|
|
15
15
|
//
|
|
16
|
-
// Asks the MCP client for input
|
|
16
|
+
// Asks the MCP client for input. Input is only
|
|
17
17
|
// collected from 2026-07-28 clients with form elicitation; every other client
|
|
18
18
|
// (and a non-MCP run) resolves to {action: "cancel"}. The `input` block is the
|
|
19
19
|
// same grammar as a tool's `input` block.
|
|
@@ -5,7 +5,7 @@ import { CompleteToken, OauthToken, RevokeToken } from "../../../lexer/mcp.js";
|
|
|
5
5
|
import { DotToken, NewlineToken } from "../../../lexer/tokens.js";
|
|
6
6
|
import { getVarName } from "../../generic/utils.js";
|
|
7
7
|
|
|
8
|
-
// The values
|
|
8
|
+
// The values `mcp.oauth.complete` accepts for `decision`.
|
|
9
9
|
const DECISIONS = ["approve", "deny"];
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -35,9 +35,7 @@ function decisionValue($, identifier) {
|
|
|
35
35
|
$.SUBRULE5($.expressionFn, { ARGS: [identifier, {}] });
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// The `mcp.oauth.*` login hand-off statements
|
|
39
|
-
// mvp:mcp_oauth_{complete,request,revoke}, kind schemas
|
|
40
|
-
// statement/mcp.oauth.{complete,request,revoke}.yaml). Each takes an optional
|
|
38
|
+
// The `mcp.oauth.*` login hand-off statements. Each takes an optional
|
|
41
39
|
// `as $var` binding.
|
|
42
40
|
|
|
43
41
|
/**
|
|
@@ -6,8 +6,8 @@ import { ProgressToken } from "../../../lexer/mcp.js";
|
|
|
6
6
|
// message = "Working" // optional
|
|
7
7
|
// }
|
|
8
8
|
//
|
|
9
|
-
// Emits `notifications/progress` on the current MCP response stream
|
|
10
|
-
//
|
|
9
|
+
// Emits `notifications/progress` on the current MCP response stream.
|
|
10
|
+
// A value that does not strictly increase is
|
|
11
11
|
// dropped at run time. NO `as $var` binding.
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -9,8 +9,8 @@ import { PublishToken } from "../../../lexer/realtime.js";
|
|
|
9
9
|
// auth_id = $auth.id // optional
|
|
10
10
|
// }
|
|
11
11
|
//
|
|
12
|
-
// Injects a server-authored event onto a realtime v2 channel
|
|
13
|
-
//
|
|
12
|
+
// Injects a server-authored event onto a realtime v2 channel.
|
|
13
|
+
// `realtime_server` + `channel` + `data` are required;
|
|
14
14
|
// `message`/`auth_table`/`auth_id` are optional. Flat attrs (the PHP transform
|
|
15
15
|
// maps auth_table/auth_id into the nested context.auth.{dbo_id,row_id} shape).
|
|
16
16
|
// Unlike realtime.get_session it has NO `as $var` binding.
|
|
@@ -11,7 +11,7 @@ import { getVarName } from "./generic/utils.js";
|
|
|
11
11
|
|
|
12
12
|
// First-class MCP `prompt` and `resource` workspace objects (MCP 2026-07-28),
|
|
13
13
|
// attached to an `mcp_server` through its `prompts` / `resources` blocks.
|
|
14
|
-
// Grounded against
|
|
14
|
+
// Grounded against the Xano backend's XanoScript schema.
|
|
15
15
|
//
|
|
16
16
|
// prompt code_review { resource regional_order {
|
|
17
17
|
// title = "Code review" uri = "orders://{region}/{order_id}"
|
|
@@ -22,7 +22,7 @@ import { getVarName } from "./generic/utils.js";
|
|
|
22
22
|
// } response = $order
|
|
23
23
|
// }
|
|
24
24
|
|
|
25
|
-
/** The input field types a resource uri variable can be bound to
|
|
25
|
+
/** The input field types a resource uri variable can be bound to. */
|
|
26
26
|
export const RESOURCE_URI_VARIABLE_TYPES = [
|
|
27
27
|
"text",
|
|
28
28
|
"int",
|
|
@@ -39,7 +39,7 @@ const MIME_TYPE =
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Parse a resource uri: a literal URI, or an RFC 6570 LEVEL-1 template.
|
|
42
|
-
* Mirrors
|
|
42
|
+
* Mirrors the backend's resource URI parser.
|
|
43
43
|
* @param {string} uri
|
|
44
44
|
* @returns {{ problem: string|null, variables: string[] }}
|
|
45
45
|
*/
|
|
@@ -103,7 +103,7 @@ export function parseResourceUri(uri) {
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Why a resource's uri variables and its input block disagree, or null.
|
|
106
|
-
* Mirrors
|
|
106
|
+
* Mirrors the backend's check that URI variables match `input`.
|
|
107
107
|
* @param {string[]} variables
|
|
108
108
|
* @param {Object<string, {type: string, iterable: boolean}>} inputs
|
|
109
109
|
* @returns {string|null}
|
|
@@ -98,8 +98,7 @@ export function mcpServerDeclaration($) {
|
|
|
98
98
|
},
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
|
-
// Per-server OAuth (
|
|
102
|
-
// validated server-side by helper/mvp/mcpoauth/OauthConfig.php).
|
|
101
|
+
// Per-server OAuth (values are validated server-side).
|
|
103
102
|
// An assignment block like a channel's `publish`. mcp_server ONLY:
|
|
104
103
|
// deliberately absent from agent_parser.js. Mode-specific rules
|
|
105
104
|
// (hosted needs login_url, external needs issuer, ...) stay
|
package/parser/message_parser.js
CHANGED
|
@@ -82,8 +82,8 @@ export function messageDeclaration($) {
|
|
|
82
82
|
},
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
|
-
// backend
|
|
86
|
-
// documented message; round-tripped
|
|
85
|
+
// the backend emits `docs = "..."` for a
|
|
86
|
+
// documented message; round-tripped on push.
|
|
87
87
|
GATE: () => !hasDocs,
|
|
88
88
|
ALT: () => {
|
|
89
89
|
hasDocs = true;
|
|
@@ -91,7 +91,7 @@ export function messageDeclaration($) {
|
|
|
91
91
|
},
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
|
-
// backend
|
|
94
|
+
// the backend emits `active = false` for a
|
|
95
95
|
// disabled message; mirrors the sibling `channel` declaration.
|
|
96
96
|
GATE: () => !hasActive,
|
|
97
97
|
ALT: () => {
|
|
@@ -25,8 +25,7 @@ import { Identifier, NewlineToken, TypeToken } from "../lexer/tokens.js";
|
|
|
25
25
|
|
|
26
26
|
// Grammar for the top-level `microservice <name> { ... }` entity.
|
|
27
27
|
//
|
|
28
|
-
// Grounded against
|
|
29
|
-
// (extensions/MVP/includes/xano/script/kind/schema/{core,component}/*.yaml):
|
|
28
|
+
// Grounded against the Xano backend's XanoScript schema:
|
|
30
29
|
//
|
|
31
30
|
// microservice <name> {
|
|
32
31
|
// description|guid|kind|tenant_deploy = "..." # static:text (optional)
|
package/parser/parser.js
CHANGED
|
@@ -80,7 +80,7 @@ export function xanoscriptParser(text, scheme, preTokenized = null) {
|
|
|
80
80
|
}
|
|
81
81
|
for (const error of lexResult.errors) {
|
|
82
82
|
// The platform names the first character it cannot read; the lexer skips a run of them.
|
|
83
|
-
// Quoted as PHP's escapeshellarg quotes it
|
|
83
|
+
// Quoted as PHP's escapeshellarg quotes it: a `'` is written `'\''`.
|
|
84
84
|
const first = String.fromCodePoint(text.codePointAt(error.offset)).replaceAll("'", "'\\''");
|
|
85
85
|
parser.addInvalidValueError({
|
|
86
86
|
image: text.slice(error.offset, error.offset + error.length),
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* language server checks the policy's block shape only.
|
|
10
10
|
*
|
|
11
11
|
* A host fills it:
|
|
12
|
-
* -
|
|
12
|
+
* - The Xano editor sends its own live answer (`source: "live"`); a check-level problem is then an
|
|
13
13
|
* error, because the instance refuses the same save.
|
|
14
14
|
* - The Node server and offline tools load the generated snapshot (`catalogueSnapshot.js`,
|
|
15
15
|
* `source: "snapshot"`); a check-level problem is then a warning, because the snapshot can
|
|
@@ -30,7 +30,7 @@ let version = 0;
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Replace the catalogue.
|
|
33
|
-
* @param {Object|Object[]|null} catalogue `{items, document}` as the platform serves it,
|
|
33
|
+
* @param {Object|Object[]|null} catalogue `{items, document}` as the platform serves it, the editor's
|
|
34
34
|
* `{checks, document}`, or a bare list of checks; null clears it
|
|
35
35
|
* @param {{source?: "live"|"snapshot", generatedAt?: string|null}} [options]
|
|
36
36
|
*/
|
|
@@ -45,7 +45,7 @@ export const isA = (token, type) => !!token && tokenMatcher(token, type);
|
|
|
45
45
|
/** A catalogue entry's own property, never an inherited one (`constructor`, `toString`). */
|
|
46
46
|
export const own = (object, key) => object && Object.hasOwn(object, key) ? object[key] : undefined;
|
|
47
47
|
|
|
48
|
-
/**
|
|
48
|
+
/** As the platform reads a string: double quotes use C escapes, single quotes only \\ and \'. */
|
|
49
49
|
export function policyStringValue(image) {
|
|
50
50
|
if (image.startsWith('"""') || image.startsWith("'''")) {
|
|
51
51
|
return image.slice(3, -3).replace(/^\r?\n/, "").replace(/\r?\n[ ]*$/, "");
|
|
@@ -6,8 +6,7 @@ import { policyLiteralValue } from "./policyLiteral.js";
|
|
|
6
6
|
* Everything here is read from the catalogue the host loaded (see `catalogue.js`): the check
|
|
7
7
|
* ids, each check's params with their types, closed value sets, nested fields, `required`,
|
|
8
8
|
* `requires_one_of`, `minimum` and `non_empty`, and the scope fields that apply to it. Nothing
|
|
9
|
-
* names a check. The findings are worded like the platform's refusals (
|
|
10
|
-
* `coerce`, `unknownCheck`) without their `rule[i] ("KEY.Rn"): params:` prefix, since a diagnostic
|
|
9
|
+
* names a check. The findings are worded like the platform's refusals without their `rule[i] ("KEY.Rn"): params:` prefix, since a diagnostic
|
|
11
10
|
* already points at the place; they are a preview, and the platform's own answer on save decides.
|
|
12
11
|
* Where the platform stops at the first refusal, this reports every problem, one per param.
|
|
13
12
|
*
|
|
@@ -15,17 +14,17 @@ import { policyLiteralValue } from "./policyLiteral.js";
|
|
|
15
14
|
* holds at most `STRING_MAX_LENGTH` characters, and a name longer than `NEAREST_MAX_LENGTH` bytes
|
|
16
15
|
* gets no near miss.
|
|
17
16
|
*
|
|
18
|
-
* Check-specific rules the catalogue does not publish (
|
|
17
|
+
* Check-specific rules the catalogue does not publish (per-check validation,
|
|
19
18
|
* renamed ids and params, statement wildcards) are left to the platform.
|
|
20
19
|
*
|
|
21
20
|
* Whether a value is a literal at all, and duplicate names, are the block's shape, not the
|
|
22
21
|
* catalogue's: the parser reports those (policy_parser.js) and this skips such values.
|
|
23
22
|
*/
|
|
24
23
|
|
|
25
|
-
/**
|
|
24
|
+
/** The platform's limit on the longest string a string or string[] param may hold, in characters. */
|
|
26
25
|
const STRING_MAX_LENGTH = 1024;
|
|
27
26
|
|
|
28
|
-
/**
|
|
27
|
+
/** The platform's limit on the longest name, in bytes, a near miss is looked for. */
|
|
29
28
|
const NEAREST_MAX_LENGTH = 128;
|
|
30
29
|
|
|
31
30
|
/** Edit distance, as PHP's `levenshtein` (insert, delete and replace all cost 1). */
|
|
@@ -49,7 +48,7 @@ function levenshtein(a, b) {
|
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
/**
|
|
52
|
-
* The closest names to a misspelling, nearest first, at most two
|
|
51
|
+
* The closest names to a misspelling, nearest first, at most two, as the platform suggests them: within
|
|
53
52
|
* three edits, or one containing the other. A name longer than `NEAREST_MAX_LENGTH` bytes gets
|
|
54
53
|
* none, since edit distance costs the product of the two lengths per candidate.
|
|
55
54
|
* @param {string} wanted
|
|
@@ -79,7 +78,7 @@ export function didYouMean(name, candidates) {
|
|
|
79
78
|
return near.length ? `Did you mean ${near.map((n) => `"${n}"`).join(" or ")}? ` : "";
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
/**
|
|
81
|
+
/** Whether a param counts as provided: set when not null/false, not an empty or blank string, not an empty list or object. */
|
|
83
82
|
function provided(value) {
|
|
84
83
|
if (value === null || value === undefined || value === false) return false;
|
|
85
84
|
if (typeof value === "string") return value.trim() !== "";
|
|
@@ -89,7 +88,7 @@ function provided(value) {
|
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
/**
|
|
92
|
-
* A param's value as the platform validates it
|
|
91
|
+
* A param's value as the platform validates it: one left out, or written
|
|
93
92
|
* as `null` when its type is not `any`, is its default.
|
|
94
93
|
*/
|
|
95
94
|
const inForce = (literal, schema, name) =>
|
|
@@ -107,7 +106,7 @@ export function unknownCheckMessage(id, ids) {
|
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
/**
|
|
110
|
-
*
|
|
109
|
+
* Whether a value has its param's declared type and values.
|
|
111
110
|
* @returns {{token: Object, message: string}|null}
|
|
112
111
|
*/
|
|
113
112
|
function coerce(node, spec, checkId, name) {
|
|
@@ -124,7 +123,7 @@ function coerce(node, spec, checkId, name) {
|
|
|
124
123
|
if (!whole || (minimum !== undefined && node.value < minimum)) {
|
|
125
124
|
return fail(`be a whole number${minimum !== undefined ? ` of at least ${minimum}` : ""}`);
|
|
126
125
|
}
|
|
127
|
-
//
|
|
126
|
+
// The platform's integer limit: past 2^53 - 1 a whole number is no longer exact.
|
|
128
127
|
if (Math.abs(node.value) > Number.MAX_SAFE_INTEGER) {
|
|
129
128
|
return fail(`be a whole number no larger than ${Number.MAX_SAFE_INTEGER}`);
|
|
130
129
|
}
|
|
@@ -188,7 +187,7 @@ function closedProperties(spec) {
|
|
|
188
187
|
|
|
189
188
|
/**
|
|
190
189
|
* Why a set scope field cannot change what a rule of this check selects, or null when it can,
|
|
191
|
-
* read from the check's published `scope_fields` (the same reading as
|
|
190
|
+
* read from the check's published `scope_fields` (the same reading as the Xano editor's rule form).
|
|
192
191
|
*/
|
|
193
192
|
function inapplicableScope(check, name, literal) {
|
|
194
193
|
const field = (check.scope_fields ?? []).find((item) => item?.name === name);
|
package/parser/policy_parser.js
CHANGED
|
@@ -21,10 +21,10 @@ import { didYouMean, validatePolicyRule } from "./policy/validatePolicyRule.js";
|
|
|
21
21
|
// rule { <field> = <literal> } # repeating; `check` names a catalogue item, `params` its params
|
|
22
22
|
// }
|
|
23
23
|
//
|
|
24
|
-
// with the comment, named-rule and literal-value rules
|
|
24
|
+
// with the comment, named-rule and literal-value rules the platform enforces. It knows no
|
|
25
25
|
// field: which fields a policy and a rule may set, their types, value sets and defaults, the retired
|
|
26
26
|
// fields and reserved blocks, the key pattern and when a rule is required all come from the
|
|
27
|
-
// catalogue's `document
|
|
27
|
+
// catalogue's `document`, and every check from its `items`. With no
|
|
28
28
|
// catalogue loaded, only the shape is checked. See parser/policy/catalogue.js.
|
|
29
29
|
|
|
30
30
|
/** The platform's refusal of a named rule, after its `rule[<i>]: ` prefix. */
|
|
@@ -287,7 +287,7 @@ function fieldProblem(name, node, spec) {
|
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
/**
|
|
290
|
-
* A rule's params: an object of literal values (
|
|
290
|
+
* A rule's params: an object of literal values (a name written twice is already
|
|
291
291
|
* reported by the object grammar). The catalogue then says what the object may hold.
|
|
292
292
|
*/
|
|
293
293
|
function checkParamsShape(node, report) {
|
|
@@ -33,7 +33,7 @@ export function realtimeChannelDeclaration($) {
|
|
|
33
33
|
anonymous_clients: "[boolean]",
|
|
34
34
|
nested_channels: "[boolean]",
|
|
35
35
|
// The CLI pull emits `message_history: history ?? 0` (an arbitrary
|
|
36
|
-
// integer
|
|
36
|
+
// integer), so
|
|
37
37
|
// accept any numeric value rather than a fixed enum. Kept typed
|
|
38
38
|
// ("[number]") so a non-numeric value still surfaces a diagnostic.
|
|
39
39
|
message_history: "[number]",
|