@baasix/mcp 0.1.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 +667 -0
- package/baasix/config.js +120 -0
- package/baasix/index.js +2694 -0
- package/package.json +59 -0
package/baasix/index.js
ADDED
|
@@ -0,0 +1,2694 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import axios from "axios";
|
|
6
|
+
import { loadEnvironmentConfig } from "./config.js";
|
|
7
|
+
|
|
8
|
+
// Load configuration
|
|
9
|
+
const config = loadEnvironmentConfig();
|
|
10
|
+
|
|
11
|
+
// Configuration from loaded config
|
|
12
|
+
const BAASIX_URL = config.BAASIX_URL || "http://localhost:8056";
|
|
13
|
+
const BAASIX_AUTH_TOKEN = config.BAASIX_AUTH_TOKEN;
|
|
14
|
+
const BAASIX_EMAIL = config.BAASIX_EMAIL;
|
|
15
|
+
const BAASIX_PASSWORD = config.BAASIX_PASSWORD;
|
|
16
|
+
|
|
17
|
+
// Authentication state
|
|
18
|
+
let authToken = null;
|
|
19
|
+
let authExpiry = null;
|
|
20
|
+
|
|
21
|
+
// Helper function to get valid auth token
|
|
22
|
+
async function getAuthToken() {
|
|
23
|
+
// Priority 1: Use provided token if available
|
|
24
|
+
if (BAASIX_AUTH_TOKEN) {
|
|
25
|
+
return BAASIX_AUTH_TOKEN;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Priority 2: Check if current token is still valid
|
|
29
|
+
if (authToken && authExpiry && Date.now() < authExpiry) {
|
|
30
|
+
return authToken;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Priority 3: Auto-authenticate using email/password
|
|
34
|
+
if (BAASIX_EMAIL && BAASIX_PASSWORD) {
|
|
35
|
+
try {
|
|
36
|
+
const response = await axios.post(`${BAASIX_URL}/auth/login`, {
|
|
37
|
+
email: BAASIX_EMAIL,
|
|
38
|
+
password: BAASIX_PASSWORD,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
authToken = response.data.token;
|
|
42
|
+
authExpiry = Date.now() + 60 * 60 * 1000; // 1 hour
|
|
43
|
+
|
|
44
|
+
return authToken;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error(`Authentication failed: ${error.response?.data?.message || error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw new Error(
|
|
51
|
+
"No authentication method available. Please provide BAASIX_AUTH_TOKEN or BAASIX_EMAIL/BAASIX_PASSWORD"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Helper function to make authenticated requests
|
|
56
|
+
async function baasixRequest(endpoint, options = {}) {
|
|
57
|
+
const token = await getAuthToken();
|
|
58
|
+
|
|
59
|
+
const config = {
|
|
60
|
+
baseURL: BAASIX_URL,
|
|
61
|
+
...options,
|
|
62
|
+
headers: {
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
...(token && { Authorization: `Bearer ${token}` }),
|
|
65
|
+
...options.headers,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const response = await axios(endpoint, config);
|
|
71
|
+
return response.data;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
// If auth error and using auto-login, clear token and retry once
|
|
74
|
+
if (error.response?.status === 401 && authToken && !BAASIX_AUTH_TOKEN) {
|
|
75
|
+
authToken = null;
|
|
76
|
+
authExpiry = null;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const newToken = await getAuthToken();
|
|
80
|
+
if (newToken) {
|
|
81
|
+
config.headers.Authorization = `Bearer ${newToken}`;
|
|
82
|
+
const retryResponse = await axios(endpoint, config);
|
|
83
|
+
return retryResponse.data;
|
|
84
|
+
}
|
|
85
|
+
} catch (retryError) {
|
|
86
|
+
throw new Error(`Baasix API Error: ${retryError.response?.data?.message || retryError.message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
throw new Error(`Baasix API Error: ${error.response?.data?.message || error.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class BaasixMCPServer {
|
|
95
|
+
server;
|
|
96
|
+
|
|
97
|
+
constructor() {
|
|
98
|
+
this.server = new Server(
|
|
99
|
+
{
|
|
100
|
+
name: "baasix-mcp-server",
|
|
101
|
+
version: "0.1.0",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
capabilities: {
|
|
105
|
+
tools: {},
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
this.setupHandlers();
|
|
111
|
+
this.setupErrorHandling();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setupErrorHandling() {
|
|
115
|
+
this.server.onerror = (error) => {
|
|
116
|
+
console.error("[MCP Error]", error);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
process.on("SIGINT", async () => {
|
|
120
|
+
await this.server.close();
|
|
121
|
+
process.exit(0);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setupHandlers() {
|
|
126
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
127
|
+
return {
|
|
128
|
+
tools: [
|
|
129
|
+
// Schema Management Tools
|
|
130
|
+
{
|
|
131
|
+
name: "baasix_list_schemas",
|
|
132
|
+
description:
|
|
133
|
+
"Get all available collections/schemas in Baasix with optional search and pagination",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
search: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "Search term to filter schemas by collection name or schema name",
|
|
140
|
+
},
|
|
141
|
+
page: {
|
|
142
|
+
type: "number",
|
|
143
|
+
description: "Page number for pagination (default: 1)",
|
|
144
|
+
default: 1,
|
|
145
|
+
},
|
|
146
|
+
limit: {
|
|
147
|
+
type: "number",
|
|
148
|
+
description: "Number of schemas per page (default: 10)",
|
|
149
|
+
default: 10,
|
|
150
|
+
},
|
|
151
|
+
sort: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description:
|
|
154
|
+
'Sort field and direction (e.g., "collectionName:asc", "collectionName:desc")',
|
|
155
|
+
default: "collectionName:asc",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
additionalProperties: false,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "baasix_get_schema",
|
|
163
|
+
description: "Get detailed schema information for a specific collection",
|
|
164
|
+
inputSchema: {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: {
|
|
167
|
+
collection: {
|
|
168
|
+
type: "string",
|
|
169
|
+
description: "Collection name",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
required: ["collection"],
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "baasix_create_schema",
|
|
177
|
+
description: `Create a new collection schema in Baasix.
|
|
178
|
+
|
|
179
|
+
FIELD TYPES:
|
|
180
|
+
- String: VARCHAR with values.length (e.g., 255)
|
|
181
|
+
- Text: Unlimited text
|
|
182
|
+
- Integer, BigInt: Whole numbers
|
|
183
|
+
- Decimal: values.precision & values.scale
|
|
184
|
+
- Float, Real, Double: Floating point
|
|
185
|
+
- Boolean: true/false
|
|
186
|
+
- Date, DateTime, Time: Date/time
|
|
187
|
+
- UUID: With defaultValue.type: "UUIDV4"
|
|
188
|
+
- SUID: Short unique ID with defaultValue.type: "SUID"
|
|
189
|
+
- JSONB: JSON with indexing
|
|
190
|
+
- Array: values.type specifies element type
|
|
191
|
+
- Geometry, Geography: PostGIS spatial
|
|
192
|
+
- Enum: values.values array
|
|
193
|
+
|
|
194
|
+
DEFAULT VALUE TYPES:
|
|
195
|
+
- { type: "UUIDV4" } - Random UUID v4
|
|
196
|
+
- { type: "SUID" } - Short unique ID
|
|
197
|
+
- { type: "NOW" } - Current timestamp
|
|
198
|
+
- { type: "AUTOINCREMENT" } - Auto-incrementing integer
|
|
199
|
+
- { type: "SQL", value: "..." } - Custom SQL expression
|
|
200
|
+
- Static values: "active", false, 0, etc.
|
|
201
|
+
|
|
202
|
+
VALIDATION RULES:
|
|
203
|
+
- min: number - Minimum value (numeric fields)
|
|
204
|
+
- max: number - Maximum value (numeric fields)
|
|
205
|
+
- isInt: true - Must be integer
|
|
206
|
+
- notEmpty: true - String cannot be empty
|
|
207
|
+
- isEmail: true - Valid email format
|
|
208
|
+
- isUrl: true - Valid URL format
|
|
209
|
+
- len: [min, max] - String length range
|
|
210
|
+
- is/matches: "regex" - Pattern matching
|
|
211
|
+
|
|
212
|
+
SCHEMA OPTIONS:
|
|
213
|
+
- timestamps: true adds createdAt/updatedAt
|
|
214
|
+
- paranoid: true enables soft deletes (deletedAt)
|
|
215
|
+
|
|
216
|
+
EXAMPLE:
|
|
217
|
+
{
|
|
218
|
+
"name": "Product",
|
|
219
|
+
"timestamps": true,
|
|
220
|
+
"fields": {
|
|
221
|
+
"id": {"type": "UUID", "primaryKey": true, "defaultValue": {"type": "UUIDV4"}},
|
|
222
|
+
"sku": {"type": "SUID", "unique": true, "defaultValue": {"type": "SUID"}},
|
|
223
|
+
"name": {"type": "String", "allowNull": false, "values": {"length": 255}, "validate": {"notEmpty": true}},
|
|
224
|
+
"price": {"type": "Decimal", "values": {"precision": 10, "scale": 2}, "validate": {"min": 0}},
|
|
225
|
+
"email": {"type": "String", "validate": {"isEmail": true}},
|
|
226
|
+
"quantity": {"type": "Integer", "defaultValue": 0, "validate": {"isInt": true, "min": 0}}
|
|
227
|
+
}
|
|
228
|
+
}`,
|
|
229
|
+
inputSchema: {
|
|
230
|
+
type: "object",
|
|
231
|
+
properties: {
|
|
232
|
+
collection: {
|
|
233
|
+
type: "string",
|
|
234
|
+
description: "Collection name (lowercase, snake_case recommended)",
|
|
235
|
+
},
|
|
236
|
+
schema: {
|
|
237
|
+
type: "object",
|
|
238
|
+
description: "Schema definition with name, fields, timestamps, paranoid options",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
required: ["collection", "schema"],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "baasix_update_schema",
|
|
246
|
+
description: "Update an existing collection schema",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
collection: {
|
|
251
|
+
type: "string",
|
|
252
|
+
description: "Collection name",
|
|
253
|
+
},
|
|
254
|
+
schema: {
|
|
255
|
+
type: "object",
|
|
256
|
+
description: "Updated schema definition",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
required: ["collection", "schema"],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "baasix_delete_schema",
|
|
264
|
+
description: "Delete a collection schema",
|
|
265
|
+
inputSchema: {
|
|
266
|
+
type: "object",
|
|
267
|
+
properties: {
|
|
268
|
+
collection: {
|
|
269
|
+
type: "string",
|
|
270
|
+
description: "Collection name",
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
required: ["collection"],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "baasix_add_index",
|
|
278
|
+
description: "Add an index to a collection schema",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
collection: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "Collection name",
|
|
285
|
+
},
|
|
286
|
+
indexDefinition: {
|
|
287
|
+
type: "object",
|
|
288
|
+
description: "Index definition with fields and options",
|
|
289
|
+
properties: {
|
|
290
|
+
name: {
|
|
291
|
+
type: "string",
|
|
292
|
+
description: "Index name",
|
|
293
|
+
},
|
|
294
|
+
fields: {
|
|
295
|
+
type: "array",
|
|
296
|
+
items: { type: "string" },
|
|
297
|
+
description: "Array of field names to index",
|
|
298
|
+
},
|
|
299
|
+
unique: {
|
|
300
|
+
type: "boolean",
|
|
301
|
+
description: "Whether the index should be unique",
|
|
302
|
+
},
|
|
303
|
+
nullsNotDistinct: {
|
|
304
|
+
type: "boolean",
|
|
305
|
+
description: "When true, NULL values are considered equal for unique indexes (PostgreSQL 15+). Only applies when unique is true.",
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
required: ["name", "fields"],
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
required: ["collection", "indexDefinition"],
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: "baasix_remove_index",
|
|
316
|
+
description: "Remove an index from a collection schema",
|
|
317
|
+
inputSchema: {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
collection: {
|
|
321
|
+
type: "string",
|
|
322
|
+
description: "Collection name",
|
|
323
|
+
},
|
|
324
|
+
indexName: {
|
|
325
|
+
type: "string",
|
|
326
|
+
description: "Name of the index to remove",
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
required: ["collection", "indexName"],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: "baasix_create_relationship",
|
|
334
|
+
description: `Create a relationship between collections.
|
|
335
|
+
|
|
336
|
+
RELATIONSHIP TYPES:
|
|
337
|
+
- M2O (Many-to-One): Creates foreign key with auto-index. products.category → categories
|
|
338
|
+
- O2M (One-to-Many): Virtual reverse of M2O. categories.products → products
|
|
339
|
+
- O2O (One-to-One): Creates foreign key with auto-index. user.profile → profiles
|
|
340
|
+
- M2M (Many-to-Many): Creates junction table with auto-indexed FKs. products ↔ tags
|
|
341
|
+
- M2A (Many-to-Any): Polymorphic junction table. comments → posts OR products
|
|
342
|
+
|
|
343
|
+
AUTO-INDEXING:
|
|
344
|
+
All foreign key columns are automatically indexed for better query performance:
|
|
345
|
+
- M2O/O2O: Index on the FK column (e.g., category_Id)
|
|
346
|
+
- M2M/M2A: Indexes on both FK columns in junction tables
|
|
347
|
+
|
|
348
|
+
JUNCTION TABLES (M2M/M2A):
|
|
349
|
+
- Auto-generated name: {source}_{target}_{name}_junction
|
|
350
|
+
- Custom name: Use "through" property (max 63 chars for PostgreSQL)
|
|
351
|
+
- Junction tables are marked with isJunction: true in schema
|
|
352
|
+
|
|
353
|
+
EXAMPLE M2O:
|
|
354
|
+
{
|
|
355
|
+
"name": "category", // Creates category_Id field + index
|
|
356
|
+
"type": "M2O",
|
|
357
|
+
"target": "categories",
|
|
358
|
+
"alias": "products", // Reverse relation name
|
|
359
|
+
"onDelete": "CASCADE" // CASCADE, RESTRICT, SET NULL
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
EXAMPLE M2M:
|
|
363
|
+
{
|
|
364
|
+
"name": "tags",
|
|
365
|
+
"type": "M2M",
|
|
366
|
+
"target": "tags",
|
|
367
|
+
"alias": "products"
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
EXAMPLE M2M with custom junction table:
|
|
371
|
+
{
|
|
372
|
+
"name": "tags",
|
|
373
|
+
"type": "M2M",
|
|
374
|
+
"target": "tags",
|
|
375
|
+
"alias": "products",
|
|
376
|
+
"through": "product_tag_mapping" // Custom junction table name
|
|
377
|
+
}`,
|
|
378
|
+
inputSchema: {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: {
|
|
381
|
+
sourceCollection: {
|
|
382
|
+
type: "string",
|
|
383
|
+
description: "Source collection name",
|
|
384
|
+
},
|
|
385
|
+
relationshipData: {
|
|
386
|
+
type: "object",
|
|
387
|
+
description: "Relationship configuration",
|
|
388
|
+
properties: {
|
|
389
|
+
name: {
|
|
390
|
+
type: "string",
|
|
391
|
+
description: "Relationship field name (creates fieldName_Id for M2O)",
|
|
392
|
+
},
|
|
393
|
+
type: {
|
|
394
|
+
type: "string",
|
|
395
|
+
enum: ["M2O", "O2M", "O2O", "M2M", "M2A"],
|
|
396
|
+
description:
|
|
397
|
+
"M2O=Many-to-One, O2M=One-to-Many, M2M=Many-to-Many, M2A=Many-to-Any (polymorphic)",
|
|
398
|
+
},
|
|
399
|
+
target: {
|
|
400
|
+
type: "string",
|
|
401
|
+
description: "Target collection name",
|
|
402
|
+
},
|
|
403
|
+
alias: {
|
|
404
|
+
type: "string",
|
|
405
|
+
description:
|
|
406
|
+
"Alias for reverse relationship (required for bidirectional access)",
|
|
407
|
+
},
|
|
408
|
+
description: {
|
|
409
|
+
type: "string",
|
|
410
|
+
description: "Relationship description",
|
|
411
|
+
},
|
|
412
|
+
onDelete: {
|
|
413
|
+
type: "string",
|
|
414
|
+
enum: ["CASCADE", "RESTRICT", "SET NULL"],
|
|
415
|
+
description: "Delete behavior (default: CASCADE)",
|
|
416
|
+
},
|
|
417
|
+
onUpdate: {
|
|
418
|
+
type: "string",
|
|
419
|
+
enum: ["CASCADE", "RESTRICT", "SET NULL"],
|
|
420
|
+
description: "Update behavior",
|
|
421
|
+
},
|
|
422
|
+
tables: {
|
|
423
|
+
type: "array",
|
|
424
|
+
items: { type: "string" },
|
|
425
|
+
description: "Target tables for M2A (polymorphic) relationships",
|
|
426
|
+
},
|
|
427
|
+
through: {
|
|
428
|
+
type: "string",
|
|
429
|
+
description:
|
|
430
|
+
"Custom junction table name for M2M/M2A relationships (max 63 chars). If not provided, auto-generated as {source}_{target}_{name}_junction",
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
required: ["name", "type"],
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
required: ["sourceCollection", "relationshipData"],
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: "baasix_update_relationship",
|
|
441
|
+
description: "Update an existing relationship",
|
|
442
|
+
inputSchema: {
|
|
443
|
+
type: "object",
|
|
444
|
+
properties: {
|
|
445
|
+
sourceCollection: {
|
|
446
|
+
type: "string",
|
|
447
|
+
description: "Source collection name",
|
|
448
|
+
},
|
|
449
|
+
fieldName: {
|
|
450
|
+
type: "string",
|
|
451
|
+
description: "Relationship field name",
|
|
452
|
+
},
|
|
453
|
+
updateData: {
|
|
454
|
+
type: "object",
|
|
455
|
+
description: "Update data for the relationship",
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
required: ["sourceCollection", "fieldName", "updateData"],
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: "baasix_delete_relationship",
|
|
463
|
+
description: "Delete a relationship",
|
|
464
|
+
inputSchema: {
|
|
465
|
+
type: "object",
|
|
466
|
+
properties: {
|
|
467
|
+
sourceCollection: {
|
|
468
|
+
type: "string",
|
|
469
|
+
description: "Source collection name",
|
|
470
|
+
},
|
|
471
|
+
fieldName: {
|
|
472
|
+
type: "string",
|
|
473
|
+
description: "Relationship field name",
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
required: ["sourceCollection", "fieldName"],
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "baasix_export_schemas",
|
|
481
|
+
description: "Export all schemas as JSON",
|
|
482
|
+
inputSchema: {
|
|
483
|
+
type: "object",
|
|
484
|
+
properties: {},
|
|
485
|
+
additionalProperties: false,
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: "baasix_import_schemas",
|
|
490
|
+
description: "Import schemas from JSON data",
|
|
491
|
+
inputSchema: {
|
|
492
|
+
type: "object",
|
|
493
|
+
properties: {
|
|
494
|
+
schemas: {
|
|
495
|
+
type: "object",
|
|
496
|
+
description: "Schema data to import",
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
required: ["schemas"],
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
// Item Management Tools
|
|
504
|
+
{
|
|
505
|
+
name: "baasix_list_items",
|
|
506
|
+
description: `Query items from a collection with powerful filtering, sorting, pagination, relations, and aggregation.
|
|
507
|
+
|
|
508
|
+
FILTER OPERATORS (50+):
|
|
509
|
+
- Comparison: eq, neq, gt, gte, lt, lte
|
|
510
|
+
- String: contains, icontains, startswith, endswith, like, ilike, regex
|
|
511
|
+
- Null: isNull (true/false), empty (true/false)
|
|
512
|
+
- List: in, nin, between, nbetween
|
|
513
|
+
- Array: arraycontains, arraycontainsany, arraylength, arrayempty
|
|
514
|
+
- JSONB: jsoncontains, jsonhaskey, jsonhasanykeys, jsonhasallkeys, jsonpath
|
|
515
|
+
- Geospatial: dwithin, intersects, contains, within, overlaps
|
|
516
|
+
- Logical: AND, OR, NOT
|
|
517
|
+
|
|
518
|
+
DYNAMIC VARIABLES:
|
|
519
|
+
- $CURRENT_USER: Current user's ID
|
|
520
|
+
- $NOW: Current timestamp
|
|
521
|
+
- $NOW-DAYS_7: 7 days ago
|
|
522
|
+
- $NOW+MONTHS_1: 1 month from now
|
|
523
|
+
|
|
524
|
+
FILTER EXAMPLES:
|
|
525
|
+
- {"status": {"eq": "active"}}
|
|
526
|
+
- {"AND": [{"price": {"gte": 10}}, {"price": {"lte": 100}}]}
|
|
527
|
+
- {"tags": {"arraycontains": ["featured"]}}
|
|
528
|
+
- {"author_Id": {"eq": "$CURRENT_USER"}}
|
|
529
|
+
- {"category.name": {"eq": "Electronics"}} (relation filter)`,
|
|
530
|
+
inputSchema: {
|
|
531
|
+
type: "object",
|
|
532
|
+
properties: {
|
|
533
|
+
collection: {
|
|
534
|
+
type: "string",
|
|
535
|
+
description: "Collection name",
|
|
536
|
+
},
|
|
537
|
+
filter: {
|
|
538
|
+
type: "object",
|
|
539
|
+
description:
|
|
540
|
+
"Filter criteria using operators like eq, neq, gt, gte, lt, lte, contains, in, between, etc.",
|
|
541
|
+
},
|
|
542
|
+
fields: {
|
|
543
|
+
type: "array",
|
|
544
|
+
items: { type: "string" },
|
|
545
|
+
description:
|
|
546
|
+
'Fields to return. Use ["*"] for all, ["*", "relation.*"] to include relations',
|
|
547
|
+
},
|
|
548
|
+
sort: {
|
|
549
|
+
type: "string",
|
|
550
|
+
description: 'Sort field and direction (e.g., "createdAt:desc", "name:asc")',
|
|
551
|
+
},
|
|
552
|
+
page: {
|
|
553
|
+
type: "number",
|
|
554
|
+
description: "Page number (default: 1)",
|
|
555
|
+
default: 1,
|
|
556
|
+
},
|
|
557
|
+
limit: {
|
|
558
|
+
type: "number",
|
|
559
|
+
description: "Items per page (default: 10, use -1 for all)",
|
|
560
|
+
default: 10,
|
|
561
|
+
},
|
|
562
|
+
search: {
|
|
563
|
+
type: "string",
|
|
564
|
+
description: "Full-text search query",
|
|
565
|
+
},
|
|
566
|
+
searchFields: {
|
|
567
|
+
type: "array",
|
|
568
|
+
items: { type: "string" },
|
|
569
|
+
description: 'Fields to search in (e.g., ["name", "description"])',
|
|
570
|
+
},
|
|
571
|
+
aggregate: {
|
|
572
|
+
type: "object",
|
|
573
|
+
description:
|
|
574
|
+
'Aggregation functions: {alias: {function: "sum|avg|count|min|max", field: "fieldName"}}',
|
|
575
|
+
},
|
|
576
|
+
groupBy: {
|
|
577
|
+
type: "array",
|
|
578
|
+
items: { type: "string" },
|
|
579
|
+
description: "Fields to group by for aggregation",
|
|
580
|
+
},
|
|
581
|
+
relConditions: {
|
|
582
|
+
type: "object",
|
|
583
|
+
description:
|
|
584
|
+
'Filter conditions for related records: {"reviews": {"approved": {"eq": true}}}',
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
required: ["collection"],
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "baasix_get_item",
|
|
592
|
+
description: "Get a specific item by ID from a collection, optionally including related data",
|
|
593
|
+
inputSchema: {
|
|
594
|
+
type: "object",
|
|
595
|
+
properties: {
|
|
596
|
+
collection: {
|
|
597
|
+
type: "string",
|
|
598
|
+
description: "Collection name",
|
|
599
|
+
},
|
|
600
|
+
id: {
|
|
601
|
+
type: "string",
|
|
602
|
+
description: "Item ID (UUID)",
|
|
603
|
+
},
|
|
604
|
+
fields: {
|
|
605
|
+
type: "array",
|
|
606
|
+
items: { type: "string" },
|
|
607
|
+
description: 'Fields to return. Use ["*", "relation.*"] to include relations',
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
required: ["collection", "id"],
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
name: "baasix_create_item",
|
|
615
|
+
description: "Create a new item in a collection",
|
|
616
|
+
inputSchema: {
|
|
617
|
+
type: "object",
|
|
618
|
+
properties: {
|
|
619
|
+
collection: {
|
|
620
|
+
type: "string",
|
|
621
|
+
description: "Collection name",
|
|
622
|
+
},
|
|
623
|
+
data: {
|
|
624
|
+
type: "object",
|
|
625
|
+
description: "Item data",
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
required: ["collection", "data"],
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: "baasix_update_item",
|
|
633
|
+
description: "Update an existing item in a collection",
|
|
634
|
+
inputSchema: {
|
|
635
|
+
type: "object",
|
|
636
|
+
properties: {
|
|
637
|
+
collection: {
|
|
638
|
+
type: "string",
|
|
639
|
+
description: "Collection name",
|
|
640
|
+
},
|
|
641
|
+
id: {
|
|
642
|
+
type: "string",
|
|
643
|
+
description: "Item ID",
|
|
644
|
+
},
|
|
645
|
+
data: {
|
|
646
|
+
type: "object",
|
|
647
|
+
description: "Updated item data",
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
required: ["collection", "id", "data"],
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: "baasix_delete_item",
|
|
655
|
+
description: "Delete an item from a collection",
|
|
656
|
+
inputSchema: {
|
|
657
|
+
type: "object",
|
|
658
|
+
properties: {
|
|
659
|
+
collection: {
|
|
660
|
+
type: "string",
|
|
661
|
+
description: "Collection name",
|
|
662
|
+
},
|
|
663
|
+
id: {
|
|
664
|
+
type: "string",
|
|
665
|
+
description: "Item ID",
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
required: ["collection", "id"],
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
// File Management Tools
|
|
673
|
+
{
|
|
674
|
+
name: "baasix_list_files",
|
|
675
|
+
description: "List files with metadata and optional filtering",
|
|
676
|
+
inputSchema: {
|
|
677
|
+
type: "object",
|
|
678
|
+
properties: {
|
|
679
|
+
filter: {
|
|
680
|
+
type: "object",
|
|
681
|
+
description: "Filter criteria",
|
|
682
|
+
},
|
|
683
|
+
page: {
|
|
684
|
+
type: "number",
|
|
685
|
+
description: "Page number (default: 1)",
|
|
686
|
+
default: 1,
|
|
687
|
+
},
|
|
688
|
+
limit: {
|
|
689
|
+
type: "number",
|
|
690
|
+
description: "Files per page (default: 10)",
|
|
691
|
+
default: 10,
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
name: "baasix_get_file_info",
|
|
698
|
+
description: "Get detailed information about a specific file",
|
|
699
|
+
inputSchema: {
|
|
700
|
+
type: "object",
|
|
701
|
+
properties: {
|
|
702
|
+
id: {
|
|
703
|
+
type: "string",
|
|
704
|
+
description: "File ID",
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
required: ["id"],
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
name: "baasix_delete_file",
|
|
712
|
+
description: "Delete a file",
|
|
713
|
+
inputSchema: {
|
|
714
|
+
type: "object",
|
|
715
|
+
properties: {
|
|
716
|
+
id: {
|
|
717
|
+
type: "string",
|
|
718
|
+
description: "File ID",
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
required: ["id"],
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
|
|
725
|
+
// Authentication Tools
|
|
726
|
+
{
|
|
727
|
+
name: "baasix_auth_status",
|
|
728
|
+
description: "Check the current authentication status and token validity",
|
|
729
|
+
inputSchema: {
|
|
730
|
+
type: "object",
|
|
731
|
+
properties: {},
|
|
732
|
+
additionalProperties: false,
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
name: "baasix_refresh_auth",
|
|
737
|
+
description: "Force refresh the authentication token (only works for email/password auth)",
|
|
738
|
+
inputSchema: {
|
|
739
|
+
type: "object",
|
|
740
|
+
properties: {},
|
|
741
|
+
additionalProperties: false,
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
|
|
745
|
+
// Reports and Analytics Tools
|
|
746
|
+
{
|
|
747
|
+
name: "baasix_generate_report",
|
|
748
|
+
description: "Generate reports with grouping and aggregation for a collection",
|
|
749
|
+
inputSchema: {
|
|
750
|
+
type: "object",
|
|
751
|
+
properties: {
|
|
752
|
+
collection: {
|
|
753
|
+
type: "string",
|
|
754
|
+
description: "Collection name",
|
|
755
|
+
},
|
|
756
|
+
groupBy: {
|
|
757
|
+
type: "string",
|
|
758
|
+
description: "Field to group by",
|
|
759
|
+
},
|
|
760
|
+
filter: {
|
|
761
|
+
type: "object",
|
|
762
|
+
description: "Filter criteria",
|
|
763
|
+
},
|
|
764
|
+
dateRange: {
|
|
765
|
+
type: "object",
|
|
766
|
+
properties: {
|
|
767
|
+
start: { type: "string" },
|
|
768
|
+
end: { type: "string" },
|
|
769
|
+
},
|
|
770
|
+
description: "Date range filter",
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
required: ["collection"],
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
name: "baasix_collection_stats",
|
|
778
|
+
description: "Get collection statistics and analytics",
|
|
779
|
+
inputSchema: {
|
|
780
|
+
type: "object",
|
|
781
|
+
properties: {
|
|
782
|
+
collections: {
|
|
783
|
+
type: "array",
|
|
784
|
+
items: { type: "string" },
|
|
785
|
+
description: "Specific collections to get stats for",
|
|
786
|
+
},
|
|
787
|
+
timeframe: {
|
|
788
|
+
type: "string",
|
|
789
|
+
description: 'Timeframe for stats (e.g., "24h", "7d", "30d")',
|
|
790
|
+
},
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
},
|
|
794
|
+
|
|
795
|
+
// Notification Tools
|
|
796
|
+
{
|
|
797
|
+
name: "baasix_list_notifications",
|
|
798
|
+
description: "List notifications for the authenticated user",
|
|
799
|
+
inputSchema: {
|
|
800
|
+
type: "object",
|
|
801
|
+
properties: {
|
|
802
|
+
page: {
|
|
803
|
+
type: "number",
|
|
804
|
+
description: "Page number (default: 1)",
|
|
805
|
+
default: 1,
|
|
806
|
+
},
|
|
807
|
+
limit: {
|
|
808
|
+
type: "number",
|
|
809
|
+
description: "Notifications per page (default: 10)",
|
|
810
|
+
default: 10,
|
|
811
|
+
},
|
|
812
|
+
seen: {
|
|
813
|
+
type: "boolean",
|
|
814
|
+
description: "Filter by seen status",
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
name: "baasix_send_notification",
|
|
821
|
+
description: "Send a notification to specified users",
|
|
822
|
+
inputSchema: {
|
|
823
|
+
type: "object",
|
|
824
|
+
properties: {
|
|
825
|
+
recipients: {
|
|
826
|
+
type: "array",
|
|
827
|
+
items: { type: "string" },
|
|
828
|
+
description: "Array of user IDs to send notification to",
|
|
829
|
+
},
|
|
830
|
+
title: {
|
|
831
|
+
type: "string",
|
|
832
|
+
description: "Notification title",
|
|
833
|
+
},
|
|
834
|
+
message: {
|
|
835
|
+
type: "string",
|
|
836
|
+
description: "Notification message",
|
|
837
|
+
},
|
|
838
|
+
type: {
|
|
839
|
+
type: "string",
|
|
840
|
+
description: "Notification type",
|
|
841
|
+
default: "info",
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
required: ["recipients", "title", "message"],
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
name: "baasix_mark_notification_seen",
|
|
849
|
+
description: "Mark a notification as seen",
|
|
850
|
+
inputSchema: {
|
|
851
|
+
type: "object",
|
|
852
|
+
properties: {
|
|
853
|
+
id: {
|
|
854
|
+
type: "string",
|
|
855
|
+
description: "Notification ID",
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
required: ["id"],
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
|
|
862
|
+
// Settings Tools
|
|
863
|
+
{
|
|
864
|
+
name: "baasix_get_settings",
|
|
865
|
+
description: "Get application settings",
|
|
866
|
+
inputSchema: {
|
|
867
|
+
type: "object",
|
|
868
|
+
properties: {
|
|
869
|
+
key: {
|
|
870
|
+
type: "string",
|
|
871
|
+
description: "Specific setting key to retrieve",
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
},
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: "baasix_update_settings",
|
|
878
|
+
description: "Update application settings",
|
|
879
|
+
inputSchema: {
|
|
880
|
+
type: "object",
|
|
881
|
+
properties: {
|
|
882
|
+
settings: {
|
|
883
|
+
type: "object",
|
|
884
|
+
description: "Settings object to update",
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
required: ["settings"],
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
|
|
891
|
+
// Email Template Tools
|
|
892
|
+
{
|
|
893
|
+
name: "baasix_list_templates",
|
|
894
|
+
description: "List all email templates with optional filtering",
|
|
895
|
+
inputSchema: {
|
|
896
|
+
type: "object",
|
|
897
|
+
properties: {
|
|
898
|
+
filter: {
|
|
899
|
+
type: "object",
|
|
900
|
+
description: "Filter criteria (e.g., {type: {eq: 'magic_link'}})",
|
|
901
|
+
},
|
|
902
|
+
page: {
|
|
903
|
+
type: "number",
|
|
904
|
+
description: "Page number (default: 1)",
|
|
905
|
+
default: 1,
|
|
906
|
+
},
|
|
907
|
+
limit: {
|
|
908
|
+
type: "number",
|
|
909
|
+
description: "Templates per page (default: 10)",
|
|
910
|
+
default: 10,
|
|
911
|
+
},
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
name: "baasix_get_template",
|
|
917
|
+
description: "Get a specific email template by ID",
|
|
918
|
+
inputSchema: {
|
|
919
|
+
type: "object",
|
|
920
|
+
properties: {
|
|
921
|
+
id: {
|
|
922
|
+
type: "string",
|
|
923
|
+
description: "Template ID (UUID)",
|
|
924
|
+
},
|
|
925
|
+
},
|
|
926
|
+
required: ["id"],
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
name: "baasix_update_template",
|
|
931
|
+
description: `Update an email template's subject, description, or body content.
|
|
932
|
+
|
|
933
|
+
TEMPLATE TYPES:
|
|
934
|
+
- magic_link: Magic link authentication emails
|
|
935
|
+
- invite: User invitation emails
|
|
936
|
+
- password_reset: Password reset emails
|
|
937
|
+
- welcome: Welcome emails
|
|
938
|
+
- verification: Email verification emails
|
|
939
|
+
|
|
940
|
+
AVAILABLE VARIABLES:
|
|
941
|
+
- User: {{user.firstName}}, {{user.lastName}}, {{user.fullName}}, {{user.email}}
|
|
942
|
+
- Tenant: {{tenant.name}}, {{tenant.logo}}, {{tenant.website}}
|
|
943
|
+
- Auth: {{magicLink}}, {{magicCode}}, {{resetPasswordLink}}, {{inviteLink}}
|
|
944
|
+
- DateTime: {{currentDate}}, {{currentTime}}, {{currentYear}}`,
|
|
945
|
+
inputSchema: {
|
|
946
|
+
type: "object",
|
|
947
|
+
properties: {
|
|
948
|
+
id: {
|
|
949
|
+
type: "string",
|
|
950
|
+
description: "Template ID (UUID)",
|
|
951
|
+
},
|
|
952
|
+
subject: {
|
|
953
|
+
type: "string",
|
|
954
|
+
description: "Email subject line (supports variables like {{user.firstName}})",
|
|
955
|
+
},
|
|
956
|
+
description: {
|
|
957
|
+
type: "string",
|
|
958
|
+
description: "Template description",
|
|
959
|
+
},
|
|
960
|
+
body: {
|
|
961
|
+
type: "string",
|
|
962
|
+
description: "Template body as HTML string or GrapesJS project JSON",
|
|
963
|
+
},
|
|
964
|
+
isActive: {
|
|
965
|
+
type: "boolean",
|
|
966
|
+
description: "Whether the template is active",
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
required: ["id"],
|
|
970
|
+
},
|
|
971
|
+
},
|
|
972
|
+
|
|
973
|
+
// Permission Tools
|
|
974
|
+
{
|
|
975
|
+
name: "baasix_list_roles",
|
|
976
|
+
description: "List all available roles",
|
|
977
|
+
inputSchema: {
|
|
978
|
+
type: "object",
|
|
979
|
+
properties: {},
|
|
980
|
+
additionalProperties: false,
|
|
981
|
+
},
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
name: "baasix_list_permissions",
|
|
985
|
+
description: "List all permissions with optional filtering",
|
|
986
|
+
inputSchema: {
|
|
987
|
+
type: "object",
|
|
988
|
+
properties: {
|
|
989
|
+
filter: {
|
|
990
|
+
type: "object",
|
|
991
|
+
description: "Filter criteria",
|
|
992
|
+
},
|
|
993
|
+
sort: {
|
|
994
|
+
type: "string",
|
|
995
|
+
description: 'Sort field and direction (e.g., "collection:asc")',
|
|
996
|
+
},
|
|
997
|
+
page: {
|
|
998
|
+
type: "number",
|
|
999
|
+
description: "Page number (default: 1)",
|
|
1000
|
+
default: 1,
|
|
1001
|
+
},
|
|
1002
|
+
limit: {
|
|
1003
|
+
type: "number",
|
|
1004
|
+
description: "Permissions per page (default: 10)",
|
|
1005
|
+
default: 10,
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
},
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
name: "baasix_get_permission",
|
|
1012
|
+
description: "Get a specific permission by ID",
|
|
1013
|
+
inputSchema: {
|
|
1014
|
+
type: "object",
|
|
1015
|
+
properties: {
|
|
1016
|
+
id: {
|
|
1017
|
+
type: "string",
|
|
1018
|
+
description: "Permission ID",
|
|
1019
|
+
},
|
|
1020
|
+
},
|
|
1021
|
+
required: ["id"],
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
name: "baasix_get_permissions",
|
|
1026
|
+
description: "Get permissions for a specific role",
|
|
1027
|
+
inputSchema: {
|
|
1028
|
+
type: "object",
|
|
1029
|
+
properties: {
|
|
1030
|
+
role: {
|
|
1031
|
+
type: "string",
|
|
1032
|
+
description: "Role name",
|
|
1033
|
+
},
|
|
1034
|
+
},
|
|
1035
|
+
required: ["role"],
|
|
1036
|
+
},
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
name: "baasix_create_permission",
|
|
1040
|
+
description: `Create a new permission for role-based access control.
|
|
1041
|
+
|
|
1042
|
+
ACTIONS: create, read, update, delete
|
|
1043
|
+
|
|
1044
|
+
FIELDS:
|
|
1045
|
+
- ["*"] for all fields
|
|
1046
|
+
- ["name", "price"] for specific fields
|
|
1047
|
+
|
|
1048
|
+
CONDITIONS (Row-level security):
|
|
1049
|
+
- Uses same filter operators as queries
|
|
1050
|
+
- {"published": {"eq": true}} - only published records
|
|
1051
|
+
- {"author_Id": {"eq": "$CURRENT_USER"}} - only own records
|
|
1052
|
+
|
|
1053
|
+
RELCONDITIONS (Filter related data):
|
|
1054
|
+
- {"reviews": {"approved": {"eq": true}}} - only approved reviews in response
|
|
1055
|
+
|
|
1056
|
+
EXAMPLE:
|
|
1057
|
+
{
|
|
1058
|
+
"role_Id": "uuid",
|
|
1059
|
+
"collection": "products",
|
|
1060
|
+
"action": "read",
|
|
1061
|
+
"fields": ["*"],
|
|
1062
|
+
"conditions": {"published": {"eq": true}}
|
|
1063
|
+
}`,
|
|
1064
|
+
inputSchema: {
|
|
1065
|
+
type: "object",
|
|
1066
|
+
properties: {
|
|
1067
|
+
role_Id: {
|
|
1068
|
+
type: "string",
|
|
1069
|
+
description: "Role ID (UUID)",
|
|
1070
|
+
},
|
|
1071
|
+
collection: {
|
|
1072
|
+
type: "string",
|
|
1073
|
+
description: "Collection name",
|
|
1074
|
+
},
|
|
1075
|
+
action: {
|
|
1076
|
+
type: "string",
|
|
1077
|
+
enum: ["create", "read", "update", "delete"],
|
|
1078
|
+
description: "Permission action",
|
|
1079
|
+
},
|
|
1080
|
+
fields: {
|
|
1081
|
+
type: "array",
|
|
1082
|
+
items: { type: "string" },
|
|
1083
|
+
description: 'Allowed fields (["*"] for all)',
|
|
1084
|
+
},
|
|
1085
|
+
conditions: {
|
|
1086
|
+
type: "object",
|
|
1087
|
+
description: "Row-level security conditions using filter operators",
|
|
1088
|
+
},
|
|
1089
|
+
defaultValues: {
|
|
1090
|
+
type: "object",
|
|
1091
|
+
description:
|
|
1092
|
+
'Default values auto-set on creation (e.g., {"author_Id": "$CURRENT_USER"})',
|
|
1093
|
+
},
|
|
1094
|
+
relConditions: {
|
|
1095
|
+
type: "object",
|
|
1096
|
+
description: "Filter conditions for related records in response",
|
|
1097
|
+
},
|
|
1098
|
+
},
|
|
1099
|
+
required: ["role_Id", "collection", "action"],
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
name: "baasix_update_permission",
|
|
1104
|
+
description: "Update an existing permission",
|
|
1105
|
+
inputSchema: {
|
|
1106
|
+
type: "object",
|
|
1107
|
+
properties: {
|
|
1108
|
+
id: {
|
|
1109
|
+
type: "string",
|
|
1110
|
+
description: "Permission ID",
|
|
1111
|
+
},
|
|
1112
|
+
role_Id: {
|
|
1113
|
+
type: "string",
|
|
1114
|
+
description: "Role ID",
|
|
1115
|
+
},
|
|
1116
|
+
collection: {
|
|
1117
|
+
type: "string",
|
|
1118
|
+
description: "Collection name",
|
|
1119
|
+
},
|
|
1120
|
+
action: {
|
|
1121
|
+
type: "string",
|
|
1122
|
+
enum: ["create", "read", "update", "delete"],
|
|
1123
|
+
description: "Permission action",
|
|
1124
|
+
},
|
|
1125
|
+
fields: {
|
|
1126
|
+
type: "array",
|
|
1127
|
+
items: { type: "string" },
|
|
1128
|
+
description: "Allowed fields",
|
|
1129
|
+
},
|
|
1130
|
+
conditions: {
|
|
1131
|
+
type: "object",
|
|
1132
|
+
description: "Permission conditions",
|
|
1133
|
+
},
|
|
1134
|
+
defaultValues: {
|
|
1135
|
+
type: "object",
|
|
1136
|
+
description: "Default values for creation",
|
|
1137
|
+
},
|
|
1138
|
+
relConditions: {
|
|
1139
|
+
type: "object",
|
|
1140
|
+
description: "Relationship conditions",
|
|
1141
|
+
},
|
|
1142
|
+
},
|
|
1143
|
+
required: ["id"],
|
|
1144
|
+
},
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
name: "baasix_delete_permission",
|
|
1148
|
+
description: "Delete a permission",
|
|
1149
|
+
inputSchema: {
|
|
1150
|
+
type: "object",
|
|
1151
|
+
properties: {
|
|
1152
|
+
id: {
|
|
1153
|
+
type: "string",
|
|
1154
|
+
description: "Permission ID",
|
|
1155
|
+
},
|
|
1156
|
+
},
|
|
1157
|
+
required: ["id"],
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
name: "baasix_reload_permissions",
|
|
1162
|
+
description: "Reload the permission cache",
|
|
1163
|
+
inputSchema: {
|
|
1164
|
+
type: "object",
|
|
1165
|
+
properties: {},
|
|
1166
|
+
additionalProperties: false,
|
|
1167
|
+
},
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
name: "baasix_update_permissions",
|
|
1171
|
+
description: "Update permissions for a role",
|
|
1172
|
+
inputSchema: {
|
|
1173
|
+
type: "object",
|
|
1174
|
+
properties: {
|
|
1175
|
+
role: {
|
|
1176
|
+
type: "string",
|
|
1177
|
+
description: "Role name",
|
|
1178
|
+
},
|
|
1179
|
+
permissions: {
|
|
1180
|
+
type: "object",
|
|
1181
|
+
description: "Permissions object",
|
|
1182
|
+
},
|
|
1183
|
+
},
|
|
1184
|
+
required: ["role", "permissions"],
|
|
1185
|
+
},
|
|
1186
|
+
},
|
|
1187
|
+
|
|
1188
|
+
// Realtime Tools
|
|
1189
|
+
{
|
|
1190
|
+
name: "baasix_realtime_status",
|
|
1191
|
+
description: `Get the status of the realtime service including WAL configuration.
|
|
1192
|
+
|
|
1193
|
+
Returns information about:
|
|
1194
|
+
- Whether realtime is initialized and consuming WAL
|
|
1195
|
+
- PostgreSQL replication configuration (wal_level, max_replication_slots)
|
|
1196
|
+
- Publication and replication slot status
|
|
1197
|
+
- Collections with realtime enabled`,
|
|
1198
|
+
inputSchema: {
|
|
1199
|
+
type: "object",
|
|
1200
|
+
properties: {},
|
|
1201
|
+
additionalProperties: false,
|
|
1202
|
+
},
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
name: "baasix_realtime_config",
|
|
1206
|
+
description: `Check PostgreSQL replication configuration for WAL-based realtime.
|
|
1207
|
+
|
|
1208
|
+
Returns:
|
|
1209
|
+
- walLevel: Should be 'logical' for realtime to work
|
|
1210
|
+
- maxReplicationSlots: Number of available replication slots
|
|
1211
|
+
- maxWalSenders: Number of WAL sender processes
|
|
1212
|
+
- replicationSlotExists: Whether the baasix slot exists
|
|
1213
|
+
- publicationExists: Whether the baasix publication exists
|
|
1214
|
+
- tablesInPublication: List of tables currently in the publication`,
|
|
1215
|
+
inputSchema: {
|
|
1216
|
+
type: "object",
|
|
1217
|
+
properties: {},
|
|
1218
|
+
additionalProperties: false,
|
|
1219
|
+
},
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
name: "baasix_realtime_collections",
|
|
1223
|
+
description: "Get list of collections with realtime enabled and their action configurations",
|
|
1224
|
+
inputSchema: {
|
|
1225
|
+
type: "object",
|
|
1226
|
+
properties: {},
|
|
1227
|
+
additionalProperties: false,
|
|
1228
|
+
},
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
name: "baasix_realtime_enable",
|
|
1232
|
+
description: `Enable realtime for a collection. Changes will be broadcast via WebSocket when data is modified.
|
|
1233
|
+
|
|
1234
|
+
The realtime config is stored in the schema definition and can include specific actions to broadcast.`,
|
|
1235
|
+
inputSchema: {
|
|
1236
|
+
type: "object",
|
|
1237
|
+
properties: {
|
|
1238
|
+
collection: {
|
|
1239
|
+
type: "string",
|
|
1240
|
+
description: "Collection name to enable realtime for",
|
|
1241
|
+
},
|
|
1242
|
+
actions: {
|
|
1243
|
+
type: "array",
|
|
1244
|
+
items: { type: "string", enum: ["insert", "update", "delete"] },
|
|
1245
|
+
description: 'Actions to broadcast (default: ["insert", "update", "delete"])',
|
|
1246
|
+
},
|
|
1247
|
+
replicaIdentityFull: {
|
|
1248
|
+
type: "boolean",
|
|
1249
|
+
description: "Set REPLICA IDENTITY FULL for old values on UPDATE/DELETE (default: false)",
|
|
1250
|
+
},
|
|
1251
|
+
},
|
|
1252
|
+
required: ["collection"],
|
|
1253
|
+
},
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
name: "baasix_realtime_disable",
|
|
1257
|
+
description: "Disable realtime for a collection",
|
|
1258
|
+
inputSchema: {
|
|
1259
|
+
type: "object",
|
|
1260
|
+
properties: {
|
|
1261
|
+
collection: {
|
|
1262
|
+
type: "string",
|
|
1263
|
+
description: "Collection name to disable realtime for",
|
|
1264
|
+
},
|
|
1265
|
+
},
|
|
1266
|
+
required: ["collection"],
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
|
|
1270
|
+
// Utility Tools
|
|
1271
|
+
{
|
|
1272
|
+
name: "baasix_server_info",
|
|
1273
|
+
description: "Get Baasix server information and health status",
|
|
1274
|
+
inputSchema: {
|
|
1275
|
+
type: "object",
|
|
1276
|
+
properties: {},
|
|
1277
|
+
additionalProperties: false,
|
|
1278
|
+
},
|
|
1279
|
+
},
|
|
1280
|
+
{
|
|
1281
|
+
name: "baasix_sort_items",
|
|
1282
|
+
description: "Sort items within a collection (move item before/after another)",
|
|
1283
|
+
inputSchema: {
|
|
1284
|
+
type: "object",
|
|
1285
|
+
properties: {
|
|
1286
|
+
collection: {
|
|
1287
|
+
type: "string",
|
|
1288
|
+
description: "Collection name",
|
|
1289
|
+
},
|
|
1290
|
+
item: {
|
|
1291
|
+
type: "string",
|
|
1292
|
+
description: "ID of item to move",
|
|
1293
|
+
},
|
|
1294
|
+
to: {
|
|
1295
|
+
type: "string",
|
|
1296
|
+
description: "ID of target item to move before",
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
1299
|
+
required: ["collection", "item", "to"],
|
|
1300
|
+
},
|
|
1301
|
+
},
|
|
1302
|
+
|
|
1303
|
+
// Auth Tools
|
|
1304
|
+
{
|
|
1305
|
+
name: "baasix_register_user",
|
|
1306
|
+
description: "Register a new user",
|
|
1307
|
+
inputSchema: {
|
|
1308
|
+
type: "object",
|
|
1309
|
+
properties: {
|
|
1310
|
+
email: {
|
|
1311
|
+
type: "string",
|
|
1312
|
+
format: "email",
|
|
1313
|
+
description: "User email address",
|
|
1314
|
+
},
|
|
1315
|
+
password: {
|
|
1316
|
+
type: "string",
|
|
1317
|
+
description: "User password",
|
|
1318
|
+
},
|
|
1319
|
+
firstName: {
|
|
1320
|
+
type: "string",
|
|
1321
|
+
description: "User first name",
|
|
1322
|
+
},
|
|
1323
|
+
lastName: {
|
|
1324
|
+
type: "string",
|
|
1325
|
+
description: "User last name",
|
|
1326
|
+
},
|
|
1327
|
+
tenant: {
|
|
1328
|
+
type: "object",
|
|
1329
|
+
description: "Tenant information for multi-tenant mode",
|
|
1330
|
+
},
|
|
1331
|
+
roleName: {
|
|
1332
|
+
type: "string",
|
|
1333
|
+
description: "Role name to assign",
|
|
1334
|
+
},
|
|
1335
|
+
inviteToken: {
|
|
1336
|
+
type: "string",
|
|
1337
|
+
description: "Invitation token",
|
|
1338
|
+
},
|
|
1339
|
+
authMode: {
|
|
1340
|
+
type: "string",
|
|
1341
|
+
enum: ["jwt", "cookie"],
|
|
1342
|
+
description: "Authentication mode",
|
|
1343
|
+
default: "jwt",
|
|
1344
|
+
},
|
|
1345
|
+
},
|
|
1346
|
+
required: ["email", "password"],
|
|
1347
|
+
},
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
name: "baasix_login",
|
|
1351
|
+
description: "Login user with email and password",
|
|
1352
|
+
inputSchema: {
|
|
1353
|
+
type: "object",
|
|
1354
|
+
properties: {
|
|
1355
|
+
email: {
|
|
1356
|
+
type: "string",
|
|
1357
|
+
format: "email",
|
|
1358
|
+
description: "User email address",
|
|
1359
|
+
},
|
|
1360
|
+
password: {
|
|
1361
|
+
type: "string",
|
|
1362
|
+
description: "User password",
|
|
1363
|
+
},
|
|
1364
|
+
tenant_Id: {
|
|
1365
|
+
type: "string",
|
|
1366
|
+
description: "Tenant ID for multi-tenant mode",
|
|
1367
|
+
},
|
|
1368
|
+
authMode: {
|
|
1369
|
+
type: "string",
|
|
1370
|
+
enum: ["jwt", "cookie"],
|
|
1371
|
+
description: "Authentication mode",
|
|
1372
|
+
default: "jwt",
|
|
1373
|
+
},
|
|
1374
|
+
},
|
|
1375
|
+
required: ["email", "password"],
|
|
1376
|
+
},
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
name: "baasix_send_invite",
|
|
1380
|
+
description: "Send an invitation to a user",
|
|
1381
|
+
inputSchema: {
|
|
1382
|
+
type: "object",
|
|
1383
|
+
properties: {
|
|
1384
|
+
email: {
|
|
1385
|
+
type: "string",
|
|
1386
|
+
format: "email",
|
|
1387
|
+
description: "Email address to invite",
|
|
1388
|
+
},
|
|
1389
|
+
role_Id: {
|
|
1390
|
+
type: "string",
|
|
1391
|
+
description: "Role ID to assign",
|
|
1392
|
+
},
|
|
1393
|
+
tenant_Id: {
|
|
1394
|
+
type: "string",
|
|
1395
|
+
description: "Tenant ID",
|
|
1396
|
+
},
|
|
1397
|
+
link: {
|
|
1398
|
+
type: "string",
|
|
1399
|
+
format: "uri",
|
|
1400
|
+
description: "Application URL for the invitation link",
|
|
1401
|
+
},
|
|
1402
|
+
},
|
|
1403
|
+
required: ["email", "role_Id", "link"],
|
|
1404
|
+
},
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
name: "baasix_verify_invite",
|
|
1408
|
+
description: "Verify an invitation token",
|
|
1409
|
+
inputSchema: {
|
|
1410
|
+
type: "object",
|
|
1411
|
+
properties: {
|
|
1412
|
+
token: {
|
|
1413
|
+
type: "string",
|
|
1414
|
+
description: "Invitation token",
|
|
1415
|
+
},
|
|
1416
|
+
link: {
|
|
1417
|
+
type: "string",
|
|
1418
|
+
format: "uri",
|
|
1419
|
+
description: "Application URL to validate",
|
|
1420
|
+
},
|
|
1421
|
+
},
|
|
1422
|
+
required: ["token"],
|
|
1423
|
+
},
|
|
1424
|
+
},
|
|
1425
|
+
{
|
|
1426
|
+
name: "baasix_send_magic_link",
|
|
1427
|
+
description: "Send magic link or code for authentication",
|
|
1428
|
+
inputSchema: {
|
|
1429
|
+
type: "object",
|
|
1430
|
+
properties: {
|
|
1431
|
+
email: {
|
|
1432
|
+
type: "string",
|
|
1433
|
+
format: "email",
|
|
1434
|
+
description: "User email address",
|
|
1435
|
+
},
|
|
1436
|
+
link: {
|
|
1437
|
+
type: "string",
|
|
1438
|
+
format: "uri",
|
|
1439
|
+
description: "Application URL for magic link",
|
|
1440
|
+
},
|
|
1441
|
+
mode: {
|
|
1442
|
+
type: "string",
|
|
1443
|
+
enum: ["link", "code"],
|
|
1444
|
+
description: "Magic authentication mode",
|
|
1445
|
+
default: "link",
|
|
1446
|
+
},
|
|
1447
|
+
},
|
|
1448
|
+
required: ["email"],
|
|
1449
|
+
},
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
name: "baasix_get_user_tenants",
|
|
1453
|
+
description: "Get available tenants for the current user",
|
|
1454
|
+
inputSchema: {
|
|
1455
|
+
type: "object",
|
|
1456
|
+
properties: {},
|
|
1457
|
+
additionalProperties: false,
|
|
1458
|
+
},
|
|
1459
|
+
},
|
|
1460
|
+
{
|
|
1461
|
+
name: "baasix_switch_tenant",
|
|
1462
|
+
description: "Switch to a different tenant context",
|
|
1463
|
+
inputSchema: {
|
|
1464
|
+
type: "object",
|
|
1465
|
+
properties: {
|
|
1466
|
+
tenant_Id: {
|
|
1467
|
+
type: "string",
|
|
1468
|
+
description: "Tenant ID to switch to",
|
|
1469
|
+
},
|
|
1470
|
+
},
|
|
1471
|
+
required: ["tenant_Id"],
|
|
1472
|
+
},
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
name: "baasix_logout",
|
|
1476
|
+
description: "Logout the current user",
|
|
1477
|
+
inputSchema: {
|
|
1478
|
+
type: "object",
|
|
1479
|
+
properties: {},
|
|
1480
|
+
additionalProperties: false,
|
|
1481
|
+
},
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
name: "baasix_get_current_user",
|
|
1485
|
+
description: "Get current user information with role and permissions",
|
|
1486
|
+
inputSchema: {
|
|
1487
|
+
type: "object",
|
|
1488
|
+
properties: {
|
|
1489
|
+
fields: {
|
|
1490
|
+
type: "array",
|
|
1491
|
+
items: { type: "string" },
|
|
1492
|
+
description: "Specific fields to retrieve",
|
|
1493
|
+
},
|
|
1494
|
+
},
|
|
1495
|
+
},
|
|
1496
|
+
},
|
|
1497
|
+
],
|
|
1498
|
+
};
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1502
|
+
const { name, arguments: args } = request.params;
|
|
1503
|
+
|
|
1504
|
+
try {
|
|
1505
|
+
switch (name) {
|
|
1506
|
+
// Schema Management
|
|
1507
|
+
case "baasix_list_schemas":
|
|
1508
|
+
return await this.handleListSchemas(args);
|
|
1509
|
+
case "baasix_get_schema":
|
|
1510
|
+
return await this.handleGetSchema(args);
|
|
1511
|
+
case "baasix_create_schema":
|
|
1512
|
+
return await this.handleCreateSchema(args);
|
|
1513
|
+
case "baasix_update_schema":
|
|
1514
|
+
return await this.handleUpdateSchema(args);
|
|
1515
|
+
case "baasix_delete_schema":
|
|
1516
|
+
return await this.handleDeleteSchema(args);
|
|
1517
|
+
case "baasix_add_index":
|
|
1518
|
+
return await this.handleAddIndex(args);
|
|
1519
|
+
case "baasix_remove_index":
|
|
1520
|
+
return await this.handleRemoveIndex(args);
|
|
1521
|
+
case "baasix_create_relationship":
|
|
1522
|
+
return await this.handleCreateRelationship(args);
|
|
1523
|
+
case "baasix_update_relationship":
|
|
1524
|
+
return await this.handleUpdateRelationship(args);
|
|
1525
|
+
case "baasix_delete_relationship":
|
|
1526
|
+
return await this.handleDeleteRelationship(args);
|
|
1527
|
+
case "baasix_export_schemas":
|
|
1528
|
+
return await this.handleExportSchemas(args);
|
|
1529
|
+
case "baasix_import_schemas":
|
|
1530
|
+
return await this.handleImportSchemas(args);
|
|
1531
|
+
|
|
1532
|
+
// Item Management
|
|
1533
|
+
case "baasix_list_items":
|
|
1534
|
+
return await this.handleListItems(args);
|
|
1535
|
+
case "baasix_get_item":
|
|
1536
|
+
return await this.handleGetItem(args);
|
|
1537
|
+
case "baasix_create_item":
|
|
1538
|
+
return await this.handleCreateItem(args);
|
|
1539
|
+
case "baasix_update_item":
|
|
1540
|
+
return await this.handleUpdateItem(args);
|
|
1541
|
+
case "baasix_delete_item":
|
|
1542
|
+
return await this.handleDeleteItem(args);
|
|
1543
|
+
|
|
1544
|
+
// File Management
|
|
1545
|
+
case "baasix_list_files":
|
|
1546
|
+
return await this.handleListFiles(args);
|
|
1547
|
+
case "baasix_get_file_info":
|
|
1548
|
+
return await this.handleGetFileInfo(args);
|
|
1549
|
+
case "baasix_delete_file":
|
|
1550
|
+
return await this.handleDeleteFile(args);
|
|
1551
|
+
|
|
1552
|
+
// Authentication
|
|
1553
|
+
case "baasix_auth_status":
|
|
1554
|
+
return await this.handleAuthStatus(args);
|
|
1555
|
+
case "baasix_refresh_auth":
|
|
1556
|
+
return await this.handleRefreshAuth(args);
|
|
1557
|
+
|
|
1558
|
+
// Reports and Analytics
|
|
1559
|
+
case "baasix_generate_report":
|
|
1560
|
+
return await this.handleGenerateReport(args);
|
|
1561
|
+
case "baasix_collection_stats":
|
|
1562
|
+
return await this.handleGetStats(args);
|
|
1563
|
+
|
|
1564
|
+
// Notifications
|
|
1565
|
+
case "baasix_list_notifications":
|
|
1566
|
+
return await this.handleListNotifications(args);
|
|
1567
|
+
case "baasix_send_notification":
|
|
1568
|
+
return await this.handleSendNotification(args);
|
|
1569
|
+
case "baasix_mark_notification_seen":
|
|
1570
|
+
return await this.handleMarkNotificationSeen(args);
|
|
1571
|
+
|
|
1572
|
+
// Settings
|
|
1573
|
+
case "baasix_get_settings":
|
|
1574
|
+
return await this.handleGetSettings(args);
|
|
1575
|
+
case "baasix_update_settings":
|
|
1576
|
+
return await this.handleUpdateSettings(args);
|
|
1577
|
+
|
|
1578
|
+
// Email Templates
|
|
1579
|
+
case "baasix_list_templates":
|
|
1580
|
+
return await this.handleListTemplates(args);
|
|
1581
|
+
case "baasix_get_template":
|
|
1582
|
+
return await this.handleGetTemplate(args);
|
|
1583
|
+
case "baasix_update_template":
|
|
1584
|
+
return await this.handleUpdateTemplate(args);
|
|
1585
|
+
|
|
1586
|
+
// Permissions
|
|
1587
|
+
case "baasix_list_roles":
|
|
1588
|
+
return await this.handleListRoles(args);
|
|
1589
|
+
case "baasix_list_permissions":
|
|
1590
|
+
return await this.handleListPermissions(args);
|
|
1591
|
+
case "baasix_get_permission":
|
|
1592
|
+
return await this.handleGetPermission(args);
|
|
1593
|
+
case "baasix_get_permissions":
|
|
1594
|
+
return await this.handleGetPermissions(args);
|
|
1595
|
+
case "baasix_create_permission":
|
|
1596
|
+
return await this.handleCreatePermission(args);
|
|
1597
|
+
case "baasix_update_permission":
|
|
1598
|
+
return await this.handleUpdatePermission(args);
|
|
1599
|
+
case "baasix_delete_permission":
|
|
1600
|
+
return await this.handleDeletePermission(args);
|
|
1601
|
+
case "baasix_reload_permissions":
|
|
1602
|
+
return await this.handleReloadPermissions(args);
|
|
1603
|
+
case "baasix_update_permissions":
|
|
1604
|
+
return await this.handleUpdatePermissions(args);
|
|
1605
|
+
|
|
1606
|
+
// Realtime
|
|
1607
|
+
case "baasix_realtime_status":
|
|
1608
|
+
return await this.handleRealtimeStatus(args);
|
|
1609
|
+
case "baasix_realtime_config":
|
|
1610
|
+
return await this.handleRealtimeConfig(args);
|
|
1611
|
+
case "baasix_realtime_collections":
|
|
1612
|
+
return await this.handleRealtimeCollections(args);
|
|
1613
|
+
case "baasix_realtime_enable":
|
|
1614
|
+
return await this.handleRealtimeEnable(args);
|
|
1615
|
+
case "baasix_realtime_disable":
|
|
1616
|
+
return await this.handleRealtimeDisable(args);
|
|
1617
|
+
|
|
1618
|
+
// Utilities
|
|
1619
|
+
case "baasix_server_info":
|
|
1620
|
+
return await this.handleServerInfo(args);
|
|
1621
|
+
case "baasix_sort_items":
|
|
1622
|
+
return await this.handleSortItems(args);
|
|
1623
|
+
|
|
1624
|
+
// Auth
|
|
1625
|
+
case "baasix_register_user":
|
|
1626
|
+
return await this.handleRegisterUser(args);
|
|
1627
|
+
case "baasix_login":
|
|
1628
|
+
return await this.handleLogin(args);
|
|
1629
|
+
case "baasix_send_invite":
|
|
1630
|
+
return await this.handleSendInvite(args);
|
|
1631
|
+
case "baasix_verify_invite":
|
|
1632
|
+
return await this.handleVerifyInvite(args);
|
|
1633
|
+
case "baasix_send_magic_link":
|
|
1634
|
+
return await this.handleSendMagicLink(args);
|
|
1635
|
+
case "baasix_get_user_tenants":
|
|
1636
|
+
return await this.handleGetUserTenants(args);
|
|
1637
|
+
case "baasix_switch_tenant":
|
|
1638
|
+
return await this.handleSwitchTenant(args);
|
|
1639
|
+
case "baasix_logout":
|
|
1640
|
+
return await this.handleLogout(args);
|
|
1641
|
+
case "baasix_get_current_user":
|
|
1642
|
+
return await this.handleGetCurrentUser(args);
|
|
1643
|
+
|
|
1644
|
+
default:
|
|
1645
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
1646
|
+
}
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
if (error instanceof McpError) {
|
|
1649
|
+
throw error;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
console.error(`Error in tool ${name}:`, error);
|
|
1653
|
+
throw new McpError(
|
|
1654
|
+
ErrorCode.InternalError,
|
|
1655
|
+
`Tool execution failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// Schema Management Methods
|
|
1662
|
+
async handleListSchemas(args) {
|
|
1663
|
+
const { search, page, limit, sort = "collectionName:asc" } = args;
|
|
1664
|
+
const params = new URLSearchParams();
|
|
1665
|
+
if (search) params.append("search", search);
|
|
1666
|
+
if (page !== undefined) params.append("page", page.toString());
|
|
1667
|
+
if (limit !== undefined) params.append("limit", limit.toString());
|
|
1668
|
+
params.append("sort", sort);
|
|
1669
|
+
|
|
1670
|
+
const schemas = await baasixRequest(`/schemas?${params}`);
|
|
1671
|
+
return {
|
|
1672
|
+
content: [
|
|
1673
|
+
{
|
|
1674
|
+
type: "text",
|
|
1675
|
+
text: JSON.stringify(schemas, null, 2),
|
|
1676
|
+
},
|
|
1677
|
+
],
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
async handleGetSchema(args) {
|
|
1682
|
+
const { collection } = args;
|
|
1683
|
+
const schema = await baasixRequest(`/schemas/${collection}`);
|
|
1684
|
+
return {
|
|
1685
|
+
content: [
|
|
1686
|
+
{
|
|
1687
|
+
type: "text",
|
|
1688
|
+
text: JSON.stringify(schema, null, 2),
|
|
1689
|
+
},
|
|
1690
|
+
],
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
async handleCreateSchema(args) {
|
|
1695
|
+
const { collection, schema } = args;
|
|
1696
|
+
const result = await baasixRequest(`/schemas`, {
|
|
1697
|
+
method: "POST",
|
|
1698
|
+
data: { collectionName: collection, schema },
|
|
1699
|
+
});
|
|
1700
|
+
return {
|
|
1701
|
+
content: [
|
|
1702
|
+
{
|
|
1703
|
+
type: "text",
|
|
1704
|
+
text: JSON.stringify(result, null, 2),
|
|
1705
|
+
},
|
|
1706
|
+
],
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
async handleUpdateSchema(args) {
|
|
1711
|
+
const { collection, schema } = args;
|
|
1712
|
+
const result = await baasixRequest(`/schemas/${collection}`, {
|
|
1713
|
+
method: "PATCH",
|
|
1714
|
+
data: { schema },
|
|
1715
|
+
});
|
|
1716
|
+
return {
|
|
1717
|
+
content: [
|
|
1718
|
+
{
|
|
1719
|
+
type: "text",
|
|
1720
|
+
text: JSON.stringify(result, null, 2),
|
|
1721
|
+
},
|
|
1722
|
+
],
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
async handleDeleteSchema(args) {
|
|
1727
|
+
const { collection } = args;
|
|
1728
|
+
const result = await baasixRequest(`/schemas/${collection}`, {
|
|
1729
|
+
method: "DELETE",
|
|
1730
|
+
});
|
|
1731
|
+
return {
|
|
1732
|
+
content: [
|
|
1733
|
+
{
|
|
1734
|
+
type: "text",
|
|
1735
|
+
text: JSON.stringify(result, null, 2),
|
|
1736
|
+
},
|
|
1737
|
+
],
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
async handleAddIndex(args) {
|
|
1742
|
+
const { collection, indexDefinition } = args;
|
|
1743
|
+
const result = await baasixRequest(`/schemas/${collection}/indexes`, {
|
|
1744
|
+
method: "POST",
|
|
1745
|
+
data: indexDefinition,
|
|
1746
|
+
});
|
|
1747
|
+
return {
|
|
1748
|
+
content: [
|
|
1749
|
+
{
|
|
1750
|
+
type: "text",
|
|
1751
|
+
text: JSON.stringify(result, null, 2),
|
|
1752
|
+
},
|
|
1753
|
+
],
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
async handleRemoveIndex(args) {
|
|
1758
|
+
const { collection, indexName } = args;
|
|
1759
|
+
const result = await baasixRequest(`/schemas/${collection}/indexes/${indexName}`, {
|
|
1760
|
+
method: "DELETE",
|
|
1761
|
+
});
|
|
1762
|
+
return {
|
|
1763
|
+
content: [
|
|
1764
|
+
{
|
|
1765
|
+
type: "text",
|
|
1766
|
+
text: JSON.stringify(result, null, 2),
|
|
1767
|
+
},
|
|
1768
|
+
],
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
async handleCreateRelationship(args) {
|
|
1773
|
+
const { sourceCollection, relationshipData } = args;
|
|
1774
|
+
const result = await baasixRequest(`/schemas/${sourceCollection}/relationships`, {
|
|
1775
|
+
method: "POST",
|
|
1776
|
+
data: relationshipData,
|
|
1777
|
+
});
|
|
1778
|
+
return {
|
|
1779
|
+
content: [
|
|
1780
|
+
{
|
|
1781
|
+
type: "text",
|
|
1782
|
+
text: JSON.stringify(result, null, 2),
|
|
1783
|
+
},
|
|
1784
|
+
],
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
async handleUpdateRelationship(args) {
|
|
1789
|
+
const { sourceCollection, fieldName, updateData } = args;
|
|
1790
|
+
const result = await baasixRequest(`/schemas/${sourceCollection}/relationships/${fieldName}`, {
|
|
1791
|
+
method: "PATCH",
|
|
1792
|
+
data: updateData,
|
|
1793
|
+
});
|
|
1794
|
+
return {
|
|
1795
|
+
content: [
|
|
1796
|
+
{
|
|
1797
|
+
type: "text",
|
|
1798
|
+
text: JSON.stringify(result, null, 2),
|
|
1799
|
+
},
|
|
1800
|
+
],
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
async handleDeleteRelationship(args) {
|
|
1805
|
+
const { sourceCollection, fieldName } = args;
|
|
1806
|
+
const result = await baasixRequest(`/schemas/${sourceCollection}/relationships/${fieldName}`, {
|
|
1807
|
+
method: "DELETE",
|
|
1808
|
+
});
|
|
1809
|
+
return {
|
|
1810
|
+
content: [
|
|
1811
|
+
{
|
|
1812
|
+
type: "text",
|
|
1813
|
+
text: JSON.stringify(result, null, 2),
|
|
1814
|
+
},
|
|
1815
|
+
],
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
async handleExportSchemas(args) {
|
|
1820
|
+
const result = await baasixRequest("/schemas-export");
|
|
1821
|
+
return {
|
|
1822
|
+
content: [
|
|
1823
|
+
{
|
|
1824
|
+
type: "text",
|
|
1825
|
+
text: JSON.stringify(result, null, 2),
|
|
1826
|
+
},
|
|
1827
|
+
],
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
async handleImportSchemas(args) {
|
|
1832
|
+
const { schemas } = args;
|
|
1833
|
+
// Note: This is a simplified version. The actual API uses file upload.
|
|
1834
|
+
// For MCP, we'll accept JSON data directly.
|
|
1835
|
+
const result = await baasixRequest("/schemas-import", {
|
|
1836
|
+
method: "POST",
|
|
1837
|
+
data: schemas,
|
|
1838
|
+
});
|
|
1839
|
+
return {
|
|
1840
|
+
content: [
|
|
1841
|
+
{
|
|
1842
|
+
type: "text",
|
|
1843
|
+
text: JSON.stringify(result, null, 2),
|
|
1844
|
+
},
|
|
1845
|
+
],
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
// Item Management Methods
|
|
1850
|
+
async handleListItems(args) {
|
|
1851
|
+
const {
|
|
1852
|
+
collection,
|
|
1853
|
+
filter,
|
|
1854
|
+
sort,
|
|
1855
|
+
page = 1,
|
|
1856
|
+
limit = 10,
|
|
1857
|
+
fields,
|
|
1858
|
+
search,
|
|
1859
|
+
searchFields,
|
|
1860
|
+
aggregate,
|
|
1861
|
+
groupBy,
|
|
1862
|
+
relConditions,
|
|
1863
|
+
} = args;
|
|
1864
|
+
|
|
1865
|
+
const params = new URLSearchParams();
|
|
1866
|
+
if (filter) params.append("filter", JSON.stringify(filter));
|
|
1867
|
+
if (sort) params.append("sort", sort);
|
|
1868
|
+
params.append("page", page.toString());
|
|
1869
|
+
params.append("limit", limit.toString());
|
|
1870
|
+
if (fields) params.append("fields", JSON.stringify(fields));
|
|
1871
|
+
if (search) params.append("search", search);
|
|
1872
|
+
if (searchFields) params.append("searchFields", JSON.stringify(searchFields));
|
|
1873
|
+
if (aggregate) params.append("aggregate", JSON.stringify(aggregate));
|
|
1874
|
+
if (groupBy) params.append("groupBy", JSON.stringify(groupBy));
|
|
1875
|
+
if (relConditions) params.append("relConditions", JSON.stringify(relConditions));
|
|
1876
|
+
|
|
1877
|
+
const items = await baasixRequest(`/items/${collection}?${params}`);
|
|
1878
|
+
return {
|
|
1879
|
+
content: [
|
|
1880
|
+
{
|
|
1881
|
+
type: "text",
|
|
1882
|
+
text: JSON.stringify(items, null, 2),
|
|
1883
|
+
},
|
|
1884
|
+
],
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
async handleGetItem(args) {
|
|
1889
|
+
const { collection, id, fields } = args;
|
|
1890
|
+
const params = new URLSearchParams();
|
|
1891
|
+
if (fields) params.append("fields", JSON.stringify(fields));
|
|
1892
|
+
|
|
1893
|
+
const queryString = params.toString() ? `?${params}` : "";
|
|
1894
|
+
const item = await baasixRequest(`/items/${collection}/${id}${queryString}`);
|
|
1895
|
+
return {
|
|
1896
|
+
content: [
|
|
1897
|
+
{
|
|
1898
|
+
type: "text",
|
|
1899
|
+
text: JSON.stringify(item, null, 2),
|
|
1900
|
+
},
|
|
1901
|
+
],
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
async handleCreateItem(args) {
|
|
1906
|
+
const { collection, data } = args;
|
|
1907
|
+
const result = await baasixRequest(`/items/${collection}`, {
|
|
1908
|
+
method: "POST",
|
|
1909
|
+
data,
|
|
1910
|
+
});
|
|
1911
|
+
return {
|
|
1912
|
+
content: [
|
|
1913
|
+
{
|
|
1914
|
+
type: "text",
|
|
1915
|
+
text: JSON.stringify(result, null, 2),
|
|
1916
|
+
},
|
|
1917
|
+
],
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
async handleUpdateItem(args) {
|
|
1922
|
+
const { collection, id, data } = args;
|
|
1923
|
+
const result = await baasixRequest(`/items/${collection}/${id}`, {
|
|
1924
|
+
method: "PUT",
|
|
1925
|
+
data,
|
|
1926
|
+
});
|
|
1927
|
+
return {
|
|
1928
|
+
content: [
|
|
1929
|
+
{
|
|
1930
|
+
type: "text",
|
|
1931
|
+
text: JSON.stringify(result, null, 2),
|
|
1932
|
+
},
|
|
1933
|
+
],
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
async handleDeleteItem(args) {
|
|
1938
|
+
const { collection, id } = args;
|
|
1939
|
+
const result = await baasixRequest(`/items/${collection}/${id}`, {
|
|
1940
|
+
method: "DELETE",
|
|
1941
|
+
});
|
|
1942
|
+
return {
|
|
1943
|
+
content: [
|
|
1944
|
+
{
|
|
1945
|
+
type: "text",
|
|
1946
|
+
text: JSON.stringify(result, null, 2),
|
|
1947
|
+
},
|
|
1948
|
+
],
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// File Management Methods
|
|
1953
|
+
async handleListFiles(args) {
|
|
1954
|
+
const { filter, page = 1, limit = 10 } = args;
|
|
1955
|
+
const params = new URLSearchParams();
|
|
1956
|
+
if (filter) params.append("filter", JSON.stringify(filter));
|
|
1957
|
+
params.append("page", page.toString());
|
|
1958
|
+
params.append("limit", limit.toString());
|
|
1959
|
+
|
|
1960
|
+
const files = await baasixRequest(`/files?${params}`);
|
|
1961
|
+
return {
|
|
1962
|
+
content: [
|
|
1963
|
+
{
|
|
1964
|
+
type: "text",
|
|
1965
|
+
text: JSON.stringify(files, null, 2),
|
|
1966
|
+
},
|
|
1967
|
+
],
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
async handleGetFileInfo(args) {
|
|
1972
|
+
const { id } = args;
|
|
1973
|
+
const file = await baasixRequest(`/files/${id}`);
|
|
1974
|
+
return {
|
|
1975
|
+
content: [
|
|
1976
|
+
{
|
|
1977
|
+
type: "text",
|
|
1978
|
+
text: JSON.stringify(file, null, 2),
|
|
1979
|
+
},
|
|
1980
|
+
],
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
async handleDeleteFile(args) {
|
|
1985
|
+
const { id } = args;
|
|
1986
|
+
const result = await baasixRequest(`/files/${id}`, {
|
|
1987
|
+
method: "DELETE",
|
|
1988
|
+
});
|
|
1989
|
+
return {
|
|
1990
|
+
content: [
|
|
1991
|
+
{
|
|
1992
|
+
type: "text",
|
|
1993
|
+
text: JSON.stringify(result, null, 2),
|
|
1994
|
+
},
|
|
1995
|
+
],
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// Authentication Methods
|
|
2000
|
+
async handleAuthStatus(args) {
|
|
2001
|
+
try {
|
|
2002
|
+
const token = await getAuthToken();
|
|
2003
|
+
const status = {
|
|
2004
|
+
auth_method: BAASIX_AUTH_TOKEN ? "Manual Token" : BAASIX_EMAIL ? "Auto-login" : "None",
|
|
2005
|
+
configured_user: BAASIX_EMAIL || "Not configured",
|
|
2006
|
+
manual_token_provided: !!BAASIX_AUTH_TOKEN,
|
|
2007
|
+
authenticated: !!token,
|
|
2008
|
+
auto_login_expires: authExpiry ? new Date(authExpiry).toISOString() : "N/A",
|
|
2009
|
+
auto_login_valid: !!(authToken && authExpiry && Date.now() < authExpiry),
|
|
2010
|
+
};
|
|
2011
|
+
|
|
2012
|
+
return {
|
|
2013
|
+
content: [
|
|
2014
|
+
{
|
|
2015
|
+
type: "text",
|
|
2016
|
+
text: JSON.stringify(status, null, 2),
|
|
2017
|
+
},
|
|
2018
|
+
],
|
|
2019
|
+
};
|
|
2020
|
+
} catch (error) {
|
|
2021
|
+
return {
|
|
2022
|
+
content: [
|
|
2023
|
+
{
|
|
2024
|
+
type: "text",
|
|
2025
|
+
text: JSON.stringify(
|
|
2026
|
+
{
|
|
2027
|
+
error: error.message,
|
|
2028
|
+
auth_method: "None",
|
|
2029
|
+
manual_token_provided: !!BAASIX_AUTH_TOKEN,
|
|
2030
|
+
configured_user: BAASIX_EMAIL || "Not configured",
|
|
2031
|
+
},
|
|
2032
|
+
null,
|
|
2033
|
+
2
|
|
2034
|
+
),
|
|
2035
|
+
},
|
|
2036
|
+
],
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
async handleRefreshAuth(args) {
|
|
2042
|
+
if (BAASIX_AUTH_TOKEN) {
|
|
2043
|
+
return {
|
|
2044
|
+
content: [
|
|
2045
|
+
{
|
|
2046
|
+
type: "text",
|
|
2047
|
+
text: JSON.stringify(
|
|
2048
|
+
{
|
|
2049
|
+
message: "Using manual token (BAASIX_AUTH_TOKEN), no refresh needed",
|
|
2050
|
+
auth_method: "Manual Token",
|
|
2051
|
+
},
|
|
2052
|
+
null,
|
|
2053
|
+
2
|
|
2054
|
+
),
|
|
2055
|
+
},
|
|
2056
|
+
],
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
// Clear current auto-login token to force refresh
|
|
2061
|
+
authToken = null;
|
|
2062
|
+
authExpiry = null;
|
|
2063
|
+
|
|
2064
|
+
try {
|
|
2065
|
+
const token = await getAuthToken();
|
|
2066
|
+
const result = {
|
|
2067
|
+
success: !!token,
|
|
2068
|
+
user: BAASIX_EMAIL || "Not configured",
|
|
2069
|
+
token_received: !!token,
|
|
2070
|
+
expires: authExpiry ? new Date(authExpiry).toISOString() : "Unknown",
|
|
2071
|
+
auth_method: "Auto-login",
|
|
2072
|
+
};
|
|
2073
|
+
|
|
2074
|
+
return {
|
|
2075
|
+
content: [
|
|
2076
|
+
{
|
|
2077
|
+
type: "text",
|
|
2078
|
+
text: JSON.stringify(result, null, 2),
|
|
2079
|
+
},
|
|
2080
|
+
],
|
|
2081
|
+
};
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
return {
|
|
2084
|
+
content: [
|
|
2085
|
+
{
|
|
2086
|
+
type: "text",
|
|
2087
|
+
text: JSON.stringify(
|
|
2088
|
+
{
|
|
2089
|
+
success: false,
|
|
2090
|
+
error: error.message,
|
|
2091
|
+
user: BAASIX_EMAIL || "Not configured",
|
|
2092
|
+
},
|
|
2093
|
+
null,
|
|
2094
|
+
2
|
|
2095
|
+
),
|
|
2096
|
+
},
|
|
2097
|
+
],
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
// Reports and Analytics Methods
|
|
2103
|
+
async handleGenerateReport(args) {
|
|
2104
|
+
const { collection, groupBy, filter, dateRange } = args;
|
|
2105
|
+
const params = new URLSearchParams();
|
|
2106
|
+
if (groupBy) params.append("groupBy", groupBy);
|
|
2107
|
+
if (filter) params.append("filter", JSON.stringify(filter));
|
|
2108
|
+
if (dateRange) params.append("dateRange", JSON.stringify(dateRange));
|
|
2109
|
+
|
|
2110
|
+
const report = await baasixRequest(`/reports/${collection}?${params}`);
|
|
2111
|
+
return {
|
|
2112
|
+
content: [
|
|
2113
|
+
{
|
|
2114
|
+
type: "text",
|
|
2115
|
+
text: JSON.stringify(report, null, 2),
|
|
2116
|
+
},
|
|
2117
|
+
],
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
async handleGetStats(args) {
|
|
2122
|
+
const { collections, timeframe } = args;
|
|
2123
|
+
const params = new URLSearchParams();
|
|
2124
|
+
if (collections) params.append("collections", JSON.stringify(collections));
|
|
2125
|
+
if (timeframe) params.append("timeframe", timeframe);
|
|
2126
|
+
|
|
2127
|
+
const stats = await baasixRequest(`/reports/stats?${params}`);
|
|
2128
|
+
return {
|
|
2129
|
+
content: [
|
|
2130
|
+
{
|
|
2131
|
+
type: "text",
|
|
2132
|
+
text: JSON.stringify(stats, null, 2),
|
|
2133
|
+
},
|
|
2134
|
+
],
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// Notification Methods
|
|
2139
|
+
async handleListNotifications(args) {
|
|
2140
|
+
const { page = 1, limit = 10, seen } = args;
|
|
2141
|
+
const params = new URLSearchParams();
|
|
2142
|
+
params.append("page", page.toString());
|
|
2143
|
+
params.append("limit", limit.toString());
|
|
2144
|
+
if (seen !== undefined) params.append("seen", seen.toString());
|
|
2145
|
+
|
|
2146
|
+
const notifications = await baasixRequest(`/notifications?${params}`);
|
|
2147
|
+
return {
|
|
2148
|
+
content: [
|
|
2149
|
+
{
|
|
2150
|
+
type: "text",
|
|
2151
|
+
text: JSON.stringify(notifications, null, 2),
|
|
2152
|
+
},
|
|
2153
|
+
],
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
async handleSendNotification(args) {
|
|
2158
|
+
const { recipients, title, message, type = "info" } = args;
|
|
2159
|
+
const result = await baasixRequest("/notifications", {
|
|
2160
|
+
method: "POST",
|
|
2161
|
+
data: { recipients, title, message, type },
|
|
2162
|
+
});
|
|
2163
|
+
return {
|
|
2164
|
+
content: [
|
|
2165
|
+
{
|
|
2166
|
+
type: "text",
|
|
2167
|
+
text: JSON.stringify(result, null, 2),
|
|
2168
|
+
},
|
|
2169
|
+
],
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
async handleMarkNotificationSeen(args) {
|
|
2174
|
+
const { id } = args;
|
|
2175
|
+
const result = await baasixRequest(`/notifications/${id}/seen`, {
|
|
2176
|
+
method: "PUT",
|
|
2177
|
+
});
|
|
2178
|
+
return {
|
|
2179
|
+
content: [
|
|
2180
|
+
{
|
|
2181
|
+
type: "text",
|
|
2182
|
+
text: JSON.stringify(result, null, 2),
|
|
2183
|
+
},
|
|
2184
|
+
],
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// Settings Methods
|
|
2189
|
+
async handleGetSettings(args) {
|
|
2190
|
+
const { key } = args;
|
|
2191
|
+
const endpoint = key ? `/settings/${key}` : "/settings";
|
|
2192
|
+
const settings = await baasixRequest(endpoint);
|
|
2193
|
+
return {
|
|
2194
|
+
content: [
|
|
2195
|
+
{
|
|
2196
|
+
type: "text",
|
|
2197
|
+
text: JSON.stringify(settings, null, 2),
|
|
2198
|
+
},
|
|
2199
|
+
],
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
async handleUpdateSettings(args) {
|
|
2204
|
+
const { settings } = args;
|
|
2205
|
+
const result = await baasixRequest("/settings", {
|
|
2206
|
+
method: "PUT",
|
|
2207
|
+
data: settings,
|
|
2208
|
+
});
|
|
2209
|
+
return {
|
|
2210
|
+
content: [
|
|
2211
|
+
{
|
|
2212
|
+
type: "text",
|
|
2213
|
+
text: JSON.stringify(result, null, 2),
|
|
2214
|
+
},
|
|
2215
|
+
],
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// Email Template Methods
|
|
2220
|
+
async handleListTemplates(args) {
|
|
2221
|
+
const { filter, page = 1, limit = 10 } = args;
|
|
2222
|
+
const params = new URLSearchParams();
|
|
2223
|
+
if (filter) params.append("filter", JSON.stringify(filter));
|
|
2224
|
+
params.append("page", page.toString());
|
|
2225
|
+
params.append("limit", limit.toString());
|
|
2226
|
+
|
|
2227
|
+
const templates = await baasixRequest(`/items/baasix_Template?${params}`);
|
|
2228
|
+
return {
|
|
2229
|
+
content: [
|
|
2230
|
+
{
|
|
2231
|
+
type: "text",
|
|
2232
|
+
text: JSON.stringify(templates, null, 2),
|
|
2233
|
+
},
|
|
2234
|
+
],
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
async handleGetTemplate(args) {
|
|
2239
|
+
const { id } = args;
|
|
2240
|
+
const template = await baasixRequest(`/items/baasix_Template/${id}`);
|
|
2241
|
+
return {
|
|
2242
|
+
content: [
|
|
2243
|
+
{
|
|
2244
|
+
type: "text",
|
|
2245
|
+
text: JSON.stringify(template, null, 2),
|
|
2246
|
+
},
|
|
2247
|
+
],
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
async handleUpdateTemplate(args) {
|
|
2252
|
+
const { id, ...updateData } = args;
|
|
2253
|
+
const result = await baasixRequest(`/items/baasix_Template/${id}`, {
|
|
2254
|
+
method: "PATCH",
|
|
2255
|
+
data: updateData,
|
|
2256
|
+
});
|
|
2257
|
+
return {
|
|
2258
|
+
content: [
|
|
2259
|
+
{
|
|
2260
|
+
type: "text",
|
|
2261
|
+
text: JSON.stringify(result, null, 2),
|
|
2262
|
+
},
|
|
2263
|
+
],
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
// Permission Methods
|
|
2268
|
+
async handleListRoles(args) {
|
|
2269
|
+
const roles = await baasixRequest("/permissions/roles");
|
|
2270
|
+
return {
|
|
2271
|
+
content: [
|
|
2272
|
+
{
|
|
2273
|
+
type: "text",
|
|
2274
|
+
text: JSON.stringify(roles, null, 2),
|
|
2275
|
+
},
|
|
2276
|
+
],
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
async handleListPermissions(args) {
|
|
2281
|
+
const { filter, sort, page = 1, limit = 10 } = args;
|
|
2282
|
+
const params = new URLSearchParams();
|
|
2283
|
+
if (filter) params.append("filter", JSON.stringify(filter));
|
|
2284
|
+
if (sort) params.append("sort", sort);
|
|
2285
|
+
params.append("page", page.toString());
|
|
2286
|
+
params.append("limit", limit.toString());
|
|
2287
|
+
|
|
2288
|
+
const permissions = await baasixRequest(`/permissions?${params}`);
|
|
2289
|
+
return {
|
|
2290
|
+
content: [
|
|
2291
|
+
{
|
|
2292
|
+
type: "text",
|
|
2293
|
+
text: JSON.stringify(permissions, null, 2),
|
|
2294
|
+
},
|
|
2295
|
+
],
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
async handleGetPermission(args) {
|
|
2300
|
+
const { id } = args;
|
|
2301
|
+
const permission = await baasixRequest(`/permissions/${id}`);
|
|
2302
|
+
return {
|
|
2303
|
+
content: [
|
|
2304
|
+
{
|
|
2305
|
+
type: "text",
|
|
2306
|
+
text: JSON.stringify(permission, null, 2),
|
|
2307
|
+
},
|
|
2308
|
+
],
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
async handleGetPermissions(args) {
|
|
2313
|
+
const { role } = args;
|
|
2314
|
+
const permissions = await baasixRequest(`/permissions/${role}`);
|
|
2315
|
+
return {
|
|
2316
|
+
content: [
|
|
2317
|
+
{
|
|
2318
|
+
type: "text",
|
|
2319
|
+
text: JSON.stringify(permissions, null, 2),
|
|
2320
|
+
},
|
|
2321
|
+
],
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
async handleCreatePermission(args) {
|
|
2326
|
+
const { role_Id, collection, action, fields, conditions, defaultValues, relConditions } = args;
|
|
2327
|
+
const result = await baasixRequest("/permissions", {
|
|
2328
|
+
method: "POST",
|
|
2329
|
+
data: { role_Id, collection, action, fields, conditions, defaultValues, relConditions },
|
|
2330
|
+
});
|
|
2331
|
+
return {
|
|
2332
|
+
content: [
|
|
2333
|
+
{
|
|
2334
|
+
type: "text",
|
|
2335
|
+
text: JSON.stringify(result, null, 2),
|
|
2336
|
+
},
|
|
2337
|
+
],
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
async handleUpdatePermission(args) {
|
|
2342
|
+
const { id, role_Id, collection, action, fields, conditions, defaultValues, relConditions } = args;
|
|
2343
|
+
const result = await baasixRequest(`/permissions/${id}`, {
|
|
2344
|
+
method: "PATCH",
|
|
2345
|
+
data: { role_Id, collection, action, fields, conditions, defaultValues, relConditions },
|
|
2346
|
+
});
|
|
2347
|
+
return {
|
|
2348
|
+
content: [
|
|
2349
|
+
{
|
|
2350
|
+
type: "text",
|
|
2351
|
+
text: JSON.stringify(result, null, 2),
|
|
2352
|
+
},
|
|
2353
|
+
],
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
async handleDeletePermission(args) {
|
|
2358
|
+
const { id } = args;
|
|
2359
|
+
const result = await baasixRequest(`/permissions/${id}`, {
|
|
2360
|
+
method: "DELETE",
|
|
2361
|
+
});
|
|
2362
|
+
return {
|
|
2363
|
+
content: [
|
|
2364
|
+
{
|
|
2365
|
+
type: "text",
|
|
2366
|
+
text: JSON.stringify(result, null, 2),
|
|
2367
|
+
},
|
|
2368
|
+
],
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
async handleReloadPermissions(args) {
|
|
2373
|
+
const result = await baasixRequest("/permissions/reload", {
|
|
2374
|
+
method: "POST",
|
|
2375
|
+
});
|
|
2376
|
+
return {
|
|
2377
|
+
content: [
|
|
2378
|
+
{
|
|
2379
|
+
type: "text",
|
|
2380
|
+
text: JSON.stringify(result, null, 2),
|
|
2381
|
+
},
|
|
2382
|
+
],
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
async handleUpdatePermissions(args) {
|
|
2387
|
+
const { role, permissions } = args;
|
|
2388
|
+
const result = await baasixRequest(`/permissions/${role}`, {
|
|
2389
|
+
method: "PUT",
|
|
2390
|
+
data: permissions,
|
|
2391
|
+
});
|
|
2392
|
+
return {
|
|
2393
|
+
content: [
|
|
2394
|
+
{
|
|
2395
|
+
type: "text",
|
|
2396
|
+
text: JSON.stringify(result, null, 2),
|
|
2397
|
+
},
|
|
2398
|
+
],
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// Realtime Methods
|
|
2403
|
+
async handleRealtimeStatus(args) {
|
|
2404
|
+
const result = await baasixRequest("/realtime/status");
|
|
2405
|
+
return {
|
|
2406
|
+
content: [
|
|
2407
|
+
{
|
|
2408
|
+
type: "text",
|
|
2409
|
+
text: JSON.stringify(result, null, 2),
|
|
2410
|
+
},
|
|
2411
|
+
],
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
async handleRealtimeConfig(args) {
|
|
2416
|
+
const result = await baasixRequest("/realtime/config");
|
|
2417
|
+
return {
|
|
2418
|
+
content: [
|
|
2419
|
+
{
|
|
2420
|
+
type: "text",
|
|
2421
|
+
text: JSON.stringify(result, null, 2),
|
|
2422
|
+
},
|
|
2423
|
+
],
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
async handleRealtimeCollections(args) {
|
|
2428
|
+
const result = await baasixRequest("/realtime/collections");
|
|
2429
|
+
return {
|
|
2430
|
+
content: [
|
|
2431
|
+
{
|
|
2432
|
+
type: "text",
|
|
2433
|
+
text: JSON.stringify(result, null, 2),
|
|
2434
|
+
},
|
|
2435
|
+
],
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
async handleRealtimeEnable(args) {
|
|
2440
|
+
const { collection, actions, replicaIdentityFull } = args;
|
|
2441
|
+
const result = await baasixRequest(`/realtime/collections/${collection}/enable`, {
|
|
2442
|
+
method: "POST",
|
|
2443
|
+
data: {
|
|
2444
|
+
actions: actions || ["insert", "update", "delete"],
|
|
2445
|
+
replicaIdentityFull: replicaIdentityFull || false,
|
|
2446
|
+
},
|
|
2447
|
+
});
|
|
2448
|
+
return {
|
|
2449
|
+
content: [
|
|
2450
|
+
{
|
|
2451
|
+
type: "text",
|
|
2452
|
+
text: JSON.stringify(result, null, 2),
|
|
2453
|
+
},
|
|
2454
|
+
],
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
async handleRealtimeDisable(args) {
|
|
2459
|
+
const { collection } = args;
|
|
2460
|
+
const result = await baasixRequest(`/realtime/collections/${collection}/disable`, {
|
|
2461
|
+
method: "POST",
|
|
2462
|
+
});
|
|
2463
|
+
return {
|
|
2464
|
+
content: [
|
|
2465
|
+
{
|
|
2466
|
+
type: "text",
|
|
2467
|
+
text: JSON.stringify(result, null, 2),
|
|
2468
|
+
},
|
|
2469
|
+
],
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// Utility Methods
|
|
2474
|
+
async handleServerInfo(args) {
|
|
2475
|
+
try {
|
|
2476
|
+
const info = await baasixRequest("/utils/info");
|
|
2477
|
+
return {
|
|
2478
|
+
content: [
|
|
2479
|
+
{
|
|
2480
|
+
type: "text",
|
|
2481
|
+
text: JSON.stringify(
|
|
2482
|
+
{
|
|
2483
|
+
baasix_server: info,
|
|
2484
|
+
mcp_server: {
|
|
2485
|
+
name: "baasix-mcp-server",
|
|
2486
|
+
version: "0.1.0",
|
|
2487
|
+
timestamp: new Date().toISOString(),
|
|
2488
|
+
uptime: process.uptime(),
|
|
2489
|
+
memory: process.memoryUsage(),
|
|
2490
|
+
nodejs: process.version,
|
|
2491
|
+
baasix_url: BAASIX_URL,
|
|
2492
|
+
},
|
|
2493
|
+
},
|
|
2494
|
+
null,
|
|
2495
|
+
2
|
|
2496
|
+
),
|
|
2497
|
+
},
|
|
2498
|
+
],
|
|
2499
|
+
};
|
|
2500
|
+
} catch (error) {
|
|
2501
|
+
return {
|
|
2502
|
+
content: [
|
|
2503
|
+
{
|
|
2504
|
+
type: "text",
|
|
2505
|
+
text: JSON.stringify(
|
|
2506
|
+
{
|
|
2507
|
+
error: "Could not fetch Baasix server info",
|
|
2508
|
+
mcp_server: {
|
|
2509
|
+
name: "baasix-mcp-server",
|
|
2510
|
+
version: "0.1.0",
|
|
2511
|
+
timestamp: new Date().toISOString(),
|
|
2512
|
+
uptime: process.uptime(),
|
|
2513
|
+
memory: process.memoryUsage(),
|
|
2514
|
+
nodejs: process.version,
|
|
2515
|
+
baasix_url: BAASIX_URL,
|
|
2516
|
+
},
|
|
2517
|
+
},
|
|
2518
|
+
null,
|
|
2519
|
+
2
|
|
2520
|
+
),
|
|
2521
|
+
},
|
|
2522
|
+
],
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
async handleSortItems(args) {
|
|
2528
|
+
const { collection, item, to } = args;
|
|
2529
|
+
const result = await baasixRequest(`/utils/sort/${collection}`, {
|
|
2530
|
+
method: "POST",
|
|
2531
|
+
data: { item, to },
|
|
2532
|
+
});
|
|
2533
|
+
return {
|
|
2534
|
+
content: [
|
|
2535
|
+
{
|
|
2536
|
+
type: "text",
|
|
2537
|
+
text: JSON.stringify(result, null, 2),
|
|
2538
|
+
},
|
|
2539
|
+
],
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
// Auth Methods
|
|
2544
|
+
async handleRegisterUser(args) {
|
|
2545
|
+
const { email, password, firstName, lastName, tenant, roleName, inviteToken, authMode, ...customParams } = args;
|
|
2546
|
+
const result = await baasixRequest("/auth/register", {
|
|
2547
|
+
method: "POST",
|
|
2548
|
+
data: { email, password, firstName, lastName, tenant, roleName, inviteToken, authMode, ...customParams },
|
|
2549
|
+
});
|
|
2550
|
+
return {
|
|
2551
|
+
content: [
|
|
2552
|
+
{
|
|
2553
|
+
type: "text",
|
|
2554
|
+
text: JSON.stringify(result, null, 2),
|
|
2555
|
+
},
|
|
2556
|
+
],
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
async handleLogin(args) {
|
|
2561
|
+
const { email, password, tenant_Id, authMode } = args;
|
|
2562
|
+
const result = await baasixRequest("/auth/login", {
|
|
2563
|
+
method: "POST",
|
|
2564
|
+
data: { email, password, tenant_Id, authMode },
|
|
2565
|
+
});
|
|
2566
|
+
return {
|
|
2567
|
+
content: [
|
|
2568
|
+
{
|
|
2569
|
+
type: "text",
|
|
2570
|
+
text: JSON.stringify(result, null, 2),
|
|
2571
|
+
},
|
|
2572
|
+
],
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
async handleSendInvite(args) {
|
|
2577
|
+
const { email, role_Id, tenant_Id, link } = args;
|
|
2578
|
+
const result = await baasixRequest("/auth/invite", {
|
|
2579
|
+
method: "POST",
|
|
2580
|
+
data: { email, role_Id, tenant_Id, link },
|
|
2581
|
+
});
|
|
2582
|
+
return {
|
|
2583
|
+
content: [
|
|
2584
|
+
{
|
|
2585
|
+
type: "text",
|
|
2586
|
+
text: JSON.stringify(result, null, 2),
|
|
2587
|
+
},
|
|
2588
|
+
],
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
async handleVerifyInvite(args) {
|
|
2593
|
+
const { token, link } = args;
|
|
2594
|
+
const params = new URLSearchParams();
|
|
2595
|
+
if (link) params.append("link", link);
|
|
2596
|
+
|
|
2597
|
+
const result = await baasixRequest(`/auth/verify-invite/${token}?${params}`);
|
|
2598
|
+
return {
|
|
2599
|
+
content: [
|
|
2600
|
+
{
|
|
2601
|
+
type: "text",
|
|
2602
|
+
text: JSON.stringify(result, null, 2),
|
|
2603
|
+
},
|
|
2604
|
+
],
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
async handleSendMagicLink(args) {
|
|
2609
|
+
const { email, link, mode } = args;
|
|
2610
|
+
const result = await baasixRequest("/auth/magiclink", {
|
|
2611
|
+
method: "POST",
|
|
2612
|
+
data: { email, link, mode },
|
|
2613
|
+
});
|
|
2614
|
+
return {
|
|
2615
|
+
content: [
|
|
2616
|
+
{
|
|
2617
|
+
type: "text",
|
|
2618
|
+
text: JSON.stringify(result, null, 2),
|
|
2619
|
+
},
|
|
2620
|
+
],
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
async handleGetUserTenants() {
|
|
2625
|
+
const result = await baasixRequest("/auth/tenants");
|
|
2626
|
+
return {
|
|
2627
|
+
content: [
|
|
2628
|
+
{
|
|
2629
|
+
type: "text",
|
|
2630
|
+
text: JSON.stringify(result, null, 2),
|
|
2631
|
+
},
|
|
2632
|
+
],
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
async handleSwitchTenant(args) {
|
|
2637
|
+
const { tenant_Id } = args;
|
|
2638
|
+
const result = await baasixRequest("/auth/switch-tenant", {
|
|
2639
|
+
method: "POST",
|
|
2640
|
+
data: { tenant_Id },
|
|
2641
|
+
});
|
|
2642
|
+
return {
|
|
2643
|
+
content: [
|
|
2644
|
+
{
|
|
2645
|
+
type: "text",
|
|
2646
|
+
text: JSON.stringify(result, null, 2),
|
|
2647
|
+
},
|
|
2648
|
+
],
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
async handleLogout() {
|
|
2653
|
+
const result = await baasixRequest("/auth/logout");
|
|
2654
|
+
return {
|
|
2655
|
+
content: [
|
|
2656
|
+
{
|
|
2657
|
+
type: "text",
|
|
2658
|
+
text: JSON.stringify(result, null, 2),
|
|
2659
|
+
},
|
|
2660
|
+
],
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
async handleGetCurrentUser(args) {
|
|
2665
|
+
const { fields } = args;
|
|
2666
|
+
const params = new URLSearchParams();
|
|
2667
|
+
if (fields) params.append("fields", fields.join(","));
|
|
2668
|
+
|
|
2669
|
+
const result = await baasixRequest(`/auth/me?${params}`);
|
|
2670
|
+
return {
|
|
2671
|
+
content: [
|
|
2672
|
+
{
|
|
2673
|
+
type: "text",
|
|
2674
|
+
text: JSON.stringify(result, null, 2),
|
|
2675
|
+
},
|
|
2676
|
+
],
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
async run() {
|
|
2681
|
+
const transport = new StdioServerTransport();
|
|
2682
|
+
await this.server.connect(transport);
|
|
2683
|
+
console.error("Baasix MCP Server running on stdio");
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
// Export the server class
|
|
2688
|
+
export { BaasixMCPServer };
|
|
2689
|
+
|
|
2690
|
+
// Export the main function to start MCP server
|
|
2691
|
+
export const startMCPServer = async () => {
|
|
2692
|
+
const server = new BaasixMCPServer();
|
|
2693
|
+
return server.run();
|
|
2694
|
+
};
|