@baasix/mcp 0.1.4 → 0.1.6

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.
Files changed (3) hide show
  1. package/README.md +19 -2
  2. package/baasix/index.js +216 -59
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ A Model Context Protocol (MCP) server that provides Claude Desktop and other MCP
8
8
 
9
9
  ## Features
10
10
 
11
- - **57 MCP Tools** for comprehensive Baasix operations
11
+ - **52 MCP Tools** for comprehensive Baasix operations
12
12
  - **Schema Management** - Create, update, delete collections and relationships
13
13
  - **CRUD Operations** - Full item management with powerful query capabilities
14
14
  - **50+ Filter Operators** - From basic comparison to geospatial and JSONB queries
@@ -26,7 +26,7 @@ A Model Context Protocol (MCP) server that provides Claude Desktop and other MCP
26
26
  |---------|--------------------------|----------------------|
27
27
  | **Transport** | stdio | HTTP (Streamable HTTP) |
28
28
  | **Setup** | Install `@baasix/mcp` | Enable `MCP_ENABLED=true` on server |
29
- | **Authentication** | Environment variables | HTTP headers |
29
+ | **Authentication** | Environment variables | HTTP headers, query params, or body |
30
30
  | **Best For** | Claude Desktop, local dev | Production, cloud, remote servers |
31
31
  | **Process** | Runs locally | Runs on Baasix server |
32
32
  | **Config File** | `.mcp.json` | `.mcp.json` or `.vscode/mcp.json` |
@@ -139,6 +139,23 @@ For VS Code, you can use either Local MCP (stdio) or Remote MCP (HTTP).
139
139
 
140
140
  **Remote MCP (HTTP)** - Create `.vscode/mcp.json`:
141
141
 
142
+ Using query parameters (simplest):
143
+ ```jsonc
144
+ {
145
+ "servers": {
146
+ "baasix": {
147
+ "type": "http",
148
+ "url": "http://localhost:8056/mcp?email=${input:mcpEmail}&password=${input:mcpPassword}"
149
+ }
150
+ },
151
+ "inputs": [
152
+ { "id": "mcpEmail", "type": "promptString", "description": "Baasix Email" },
153
+ { "id": "mcpPassword", "type": "promptString", "description": "Password", "password": true }
154
+ ]
155
+ }
156
+ ```
157
+
158
+ Or using headers:
142
159
  ```jsonc
143
160
  {
144
161
  "servers": {
package/baasix/index.js CHANGED
@@ -160,13 +160,16 @@ class BaasixMCPServer {
160
160
  },
161
161
  {
162
162
  name: "baasix_get_schema",
163
- description: "Get detailed schema information for a specific collection",
163
+ description: `Get the full schema definition (columns, types, constraints, relationships, indexes) for a specific database table/collection.
164
+
165
+ IMPORTANT: Always call this BEFORE using baasix_update_schema to get the current schema.
166
+ The update_schema tool performs a full replacement, so you need the complete current schema to avoid losing existing fields.`,
164
167
  inputSchema: {
165
168
  type: "object",
166
169
  properties: {
167
170
  collection: {
168
171
  type: "string",
169
- description: "Collection name",
172
+ description: "Collection name of the table to inspect",
170
173
  },
171
174
  },
172
175
  required: ["collection"],
@@ -243,17 +246,60 @@ EXAMPLE:
243
246
  },
244
247
  {
245
248
  name: "baasix_update_schema",
246
- description: "Update an existing collection schema",
249
+ description: `Modify an existing collection schema — add new columns, change column types, or remove columns.
250
+ Use this when asked to "add a field", "add a column", or "alter a table".
251
+
252
+ ⚠️ CRITICAL: This performs a FULL REPLACEMENT of the schema definition, NOT a merge.
253
+ You MUST first call baasix_get_schema to retrieve the current schema, then spread ALL existing fields
254
+ and add/modify/remove only the fields you need. If you send only new fields, ALL existing fields will
255
+ be LOST from the schema definition.
256
+
257
+ CORRECT WORKFLOW to add a column:
258
+ 1. Call baasix_get_schema for the collection
259
+ 2. Copy the entire existing schema (including name, timestamps, paranoid, and ALL fields)
260
+ 3. Add/modify/remove the desired fields while keeping all other fields intact
261
+ 4. Send the complete schema to baasix_update_schema
262
+
263
+ EXAMPLE — Adding a "description" field to an existing "products" table that has id, name, price:
264
+ {
265
+ "collection": "products",
266
+ "schema": {
267
+ "name": "Product",
268
+ "timestamps": true,
269
+ "fields": {
270
+ "id": { "type": "UUID", "primaryKey": true, "defaultValue": { "type": "UUIDV4" } },
271
+ "name": { "type": "String", "allowNull": false, "values": { "length": 255 } },
272
+ "price": { "type": "Decimal", "values": { "precision": 10, "scale": 2 } },
273
+ "description": { "type": "Text", "allowNull": true }
274
+ }
275
+ }
276
+ }
277
+
278
+ ❌ WRONG — This will DELETE the id, name, and price field definitions:
279
+ {
280
+ "collection": "products",
281
+ "schema": {
282
+ "fields": {
283
+ "description": { "type": "Text", "allowNull": true }
284
+ }
285
+ }
286
+ }
287
+
288
+ To REMOVE a column: Retrieve the full schema, remove the field from the fields object, and send the complete schema without that field.
289
+ To RENAME a column: Remove the old field and add a new one with the desired name (data will NOT be migrated).
290
+ To CHANGE a field type: Update the type property on the existing field definition.
291
+
292
+ Field types and options are the same as baasix_create_schema.`,
247
293
  inputSchema: {
248
294
  type: "object",
249
295
  properties: {
250
296
  collection: {
251
297
  type: "string",
252
- description: "Collection name",
298
+ description: "Collection name of the table to modify",
253
299
  },
254
300
  schema: {
255
301
  type: "object",
256
- description: "Updated schema definition",
302
+ description: "The COMPLETE schema definition including ALL existing fields plus any additions/modifications. This REPLACES the entire schema — do NOT send partial fields.",
257
303
  },
258
304
  },
259
305
  required: ["collection", "schema"],
@@ -261,13 +307,17 @@ EXAMPLE:
261
307
  },
262
308
  {
263
309
  name: "baasix_delete_schema",
264
- description: "Delete a collection schema",
310
+ description: `DROP/DELETE an entire database table and all its data permanently.
311
+ Use this when asked to "drop a table", "delete a collection", or "remove a table".
312
+
313
+ ⚠️ WARNING: This is destructive and irreversible. The table, all its data, indexes, and relationships will be permanently deleted.
314
+ Do NOT use this to remove a column — use baasix_update_schema instead to modify the schema without the unwanted field.`,
265
315
  inputSchema: {
266
316
  type: "object",
267
317
  properties: {
268
318
  collection: {
269
319
  type: "string",
270
- description: "Collection name",
320
+ description: "Collection name of the table to permanently delete",
271
321
  },
272
322
  },
273
323
  required: ["collection"],
@@ -275,13 +325,36 @@ EXAMPLE:
275
325
  },
276
326
  {
277
327
  name: "baasix_add_index",
278
- description: "Add an index to a collection schema",
328
+ description: `Add a database index to a table for better query performance.
329
+ This is an ADDITIVE operation — it only adds the new index without affecting existing indexes or schema fields.
330
+
331
+ Supports btree (default), hash, gin, and gist index types. Can be unique.
332
+ Use this when asked to "add an index", "create an index", or "speed up queries on a field".
333
+
334
+ EXAMPLE — Add a unique index on email:
335
+ {
336
+ "collection": "users",
337
+ "indexDefinition": {
338
+ "name": "users_email_unique",
339
+ "fields": ["email"],
340
+ "unique": true
341
+ }
342
+ }
343
+
344
+ EXAMPLE — Add a composite index:
345
+ {
346
+ "collection": "orders",
347
+ "indexDefinition": {
348
+ "name": "orders_status_date",
349
+ "fields": ["status", "createdAt"]
350
+ }
351
+ }`,
279
352
  inputSchema: {
280
353
  type: "object",
281
354
  properties: {
282
355
  collection: {
283
356
  type: "string",
284
- description: "Collection name",
357
+ description: "Collection name of the table to add the index to",
285
358
  },
286
359
  indexDefinition: {
287
360
  type: "object",
@@ -289,16 +362,16 @@ EXAMPLE:
289
362
  properties: {
290
363
  name: {
291
364
  type: "string",
292
- description: "Index name",
365
+ description: "Index name (e.g., 'users_email_unique'). Use a descriptive name like {table}_{fields}_{type}",
293
366
  },
294
367
  fields: {
295
368
  type: "array",
296
369
  items: { type: "string" },
297
- description: "Array of field names to index",
370
+ description: "Array of field names to index (e.g., ['email'] or ['status', 'createdAt'] for composite)",
298
371
  },
299
372
  unique: {
300
373
  type: "boolean",
301
- description: "Whether the index should be unique",
374
+ description: "Whether the index should enforce uniqueness (default: false)",
302
375
  },
303
376
  nullsNotDistinct: {
304
377
  type: "boolean",
@@ -313,17 +386,19 @@ EXAMPLE:
313
386
  },
314
387
  {
315
388
  name: "baasix_remove_index",
316
- description: "Remove an index from a collection schema",
389
+ description: `Remove an existing index from a database table.
390
+ This only removes the index — it does NOT delete any data or columns.
391
+ Use baasix_get_schema first to see the list of existing indexes on the table if you don't know the index name.`,
317
392
  inputSchema: {
318
393
  type: "object",
319
394
  properties: {
320
395
  collection: {
321
396
  type: "string",
322
- description: "Collection name",
397
+ description: "Collection name of the table containing the index",
323
398
  },
324
399
  indexName: {
325
400
  type: "string",
326
- description: "Name of the index to remove",
401
+ description: "Name of the index to remove (use baasix_get_schema to find index names)",
327
402
  },
328
403
  },
329
404
  required: ["collection", "indexName"],
@@ -438,21 +513,39 @@ EXAMPLE M2M with custom junction table:
438
513
  },
439
514
  {
440
515
  name: "baasix_update_relationship",
441
- description: "Update an existing relationship",
516
+ description: `Modify an existing foreign key / relationship between two database tables.
517
+ Use this to change delete behavior, alias, description, or other relationship properties.
518
+ This is a PARTIAL update — only pass the properties you want to change.
519
+
520
+ Note: You CANNOT change the relationship type or target table. To do that, delete and recreate the relationship.
521
+
522
+ EXAMPLE — Change onDelete behavior:
523
+ {
524
+ "sourceCollection": "products",
525
+ "fieldName": "category",
526
+ "updateData": { "onDelete": "SET NULL" }
527
+ }
528
+
529
+ EXAMPLE — Update alias and description:
530
+ {
531
+ "sourceCollection": "products",
532
+ "fieldName": "category",
533
+ "updateData": { "alias": "items", "description": "Product category" }
534
+ }`,
442
535
  inputSchema: {
443
536
  type: "object",
444
537
  properties: {
445
538
  sourceCollection: {
446
539
  type: "string",
447
- description: "Source collection name",
540
+ description: "Source collection name that owns the relationship",
448
541
  },
449
542
  fieldName: {
450
543
  type: "string",
451
- description: "Relationship field name",
544
+ description: "Relationship field name to update (use baasix_get_schema to find relationship names)",
452
545
  },
453
546
  updateData: {
454
547
  type: "object",
455
- description: "Update data for the relationship",
548
+ description: "Only the relationship properties to change (partial update). Supports: alias, description, onDelete (CASCADE/RESTRICT/SET NULL), onUpdate",
456
549
  },
457
550
  },
458
551
  required: ["sourceCollection", "fieldName", "updateData"],
@@ -460,17 +553,23 @@ EXAMPLE M2M with custom junction table:
460
553
  },
461
554
  {
462
555
  name: "baasix_delete_relationship",
463
- description: "Delete a relationship",
556
+ description: `Remove a foreign key / relationship from a database table.
557
+ This drops the foreign key constraint and the FK column (e.g., category_Id) from the source table.
558
+ For M2M relationships, this also removes the junction table.
559
+
560
+ ⚠️ WARNING: This will remove the FK column and any data stored in it. Use baasix_get_schema first to confirm the relationship field name.
561
+
562
+ Do NOT confuse with baasix_delete_schema (which deletes an entire table).`,
464
563
  inputSchema: {
465
564
  type: "object",
466
565
  properties: {
467
566
  sourceCollection: {
468
567
  type: "string",
469
- description: "Source collection name",
568
+ description: "Source collection name that owns the relationship",
470
569
  },
471
570
  fieldName: {
472
571
  type: "string",
473
- description: "Relationship field name",
572
+ description: "Relationship field name to delete (use baasix_get_schema to find relationship names)",
474
573
  },
475
574
  },
476
575
  required: ["sourceCollection", "fieldName"],
@@ -478,7 +577,7 @@ EXAMPLE M2M with custom junction table:
478
577
  },
479
578
  {
480
579
  name: "baasix_export_schemas",
481
- description: "Export all schemas as JSON",
580
+ description: "Export all table definitions as JSON for backup or migration. Returns the complete schema definitions of every table in the database, including fields, indexes, and relationships.",
482
581
  inputSchema: {
483
582
  type: "object",
484
583
  properties: {},
@@ -487,13 +586,13 @@ EXAMPLE M2M with custom junction table:
487
586
  },
488
587
  {
489
588
  name: "baasix_import_schemas",
490
- description: "Import schemas from JSON data",
589
+ description: "Import table definitions from JSON data to recreate tables. Use this to restore schemas from a previous baasix_export_schemas backup or to migrate table structures between environments.",
491
590
  inputSchema: {
492
591
  type: "object",
493
592
  properties: {
494
593
  schemas: {
495
594
  type: "object",
496
- description: "Schema data to import",
595
+ description: "Schema data to import (format should match the output of baasix_export_schemas)",
497
596
  },
498
597
  },
499
598
  required: ["schemas"],
@@ -505,6 +604,8 @@ EXAMPLE M2M with custom junction table:
505
604
  name: "baasix_list_items",
506
605
  description: `Query items from a collection with powerful filtering, sorting, pagination, relations, and aggregation.
507
606
 
607
+ NOTE: For analytics, summaries, totals, sums, averages, counts, min/max, grouped reports, or dashboards → use baasix_generate_report instead. Use this tool (list_items) for fetching actual row data.
608
+
508
609
  FILTER OPERATORS (50+):
509
610
  - Comparison: eq, neq, gt, gte, lt, lte
510
611
  - String: contains, icontains, startswith, endswith, like, ilike, regex
@@ -745,29 +846,50 @@ FILTER EXAMPLES:
745
846
  // Reports and Analytics Tools
746
847
  {
747
848
  name: "baasix_generate_report",
748
- description: "Generate reports with grouping and aggregation for a collection",
849
+ description: `Run an aggregate/analytics query on a database table. Use this for summaries, totals, averages, counts, grouping, and any analytics query. Prefer this over baasix_list_items when the user asks for sums, counts, averages, min/max, grouped data, dashboards, or reports.
850
+
851
+ EXAMPLES:
852
+ 1. Sum of quantity: aggregate: {"total": {"function": "sum", "field": "quantity"}}
853
+ 2. Count by status: groupBy: ["status"], aggregate: {"count": {"function": "count", "field": "*"}}
854
+ 3. Average price for active products: filter: {"status": {"eq": "active"}}, aggregate: {"avg": {"function": "avg", "field": "price"}}
855
+ 4. Min/max: aggregate: {"min": {"function": "min", "field": "price"}, "max": {"function": "max", "field": "price"}}`,
749
856
  inputSchema: {
750
857
  type: "object",
751
858
  properties: {
752
859
  collection: {
753
860
  type: "string",
754
- description: "Collection name",
861
+ description: "Table/collection name to report on",
755
862
  },
756
- groupBy: {
757
- type: "string",
758
- description: "Field to group by",
863
+ fields: {
864
+ type: "array",
865
+ items: { type: "string" },
866
+ description: 'Columns to return. Default ["*"]. Use dot notation for relations: ["category.name"].',
759
867
  },
760
868
  filter: {
761
869
  type: "object",
762
- description: "Filter criteria",
870
+ description: "Row filter applied before aggregation. Same operators as baasix_list_items.",
871
+ },
872
+ sort: {
873
+ type: "array",
874
+ items: { type: "string" },
875
+ description: 'Sort results. Format: ["field:direction"]. E.g. ["count:desc", "name:asc"].',
876
+ },
877
+ limit: {
878
+ type: "number",
879
+ description: "Max rows returned. Default -1 (all). Set to limit grouped results.",
763
880
  },
764
- dateRange: {
881
+ page: {
882
+ type: "number",
883
+ description: "Page number for pagination (works with limit).",
884
+ },
885
+ aggregate: {
765
886
  type: "object",
766
- properties: {
767
- start: { type: "string" },
768
- end: { type: "string" },
769
- },
770
- description: "Date range filter",
887
+ description: 'Aggregation definitions. Each key is the result alias. Value: {function: "count|sum|avg|min|max", field: "columnName"}. E.g. {"total": {"function": "sum", "field": "quantity"}}',
888
+ },
889
+ groupBy: {
890
+ type: "array",
891
+ items: { type: "string" },
892
+ description: "Columns to group by. Required when using aggregate with breakdowns.",
771
893
  },
772
894
  },
773
895
  required: ["collection"],
@@ -775,20 +897,43 @@ FILTER EXAMPLES:
775
897
  },
776
898
  {
777
899
  name: "baasix_collection_stats",
778
- description: "Get collection statistics and analytics",
900
+ description: `Run multiple aggregate queries across different tables in a single call. Each query uses the full report engine (filter, aggregate, groupBy, etc.).
901
+
902
+ Each stats entry requires:
903
+ - name: A unique label for this stat in the results (e.g. "total_orders", "active_users")
904
+ - collection: The table to query
905
+ - query: Full report query object with fields, filter, aggregate, groupBy, sort, limit, page
906
+
907
+ EXAMPLES:
908
+ 1. Get counts: stats: [{"name": "users", "collection": "users", "query": {"aggregate": {"total": {"function": "count", "field": "*"}}}}]
909
+ 2. Multi-table: stats: [{"name": "orders", "collection": "orders", "query": {"aggregate": {"count": {"function": "count", "field": "*"}}}}, {"name": "revenue", "collection": "orders", "query": {"aggregate": {"total": {"function": "sum", "field": "amount"}}}}]`,
779
910
  inputSchema: {
780
911
  type: "object",
781
912
  properties: {
782
- collections: {
913
+ stats: {
783
914
  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")',
915
+ items: {
916
+ type: "object",
917
+ properties: {
918
+ name: {
919
+ type: "string",
920
+ description: "Unique label for this stat in the results",
921
+ },
922
+ collection: {
923
+ type: "string",
924
+ description: "Table/collection to query",
925
+ },
926
+ query: {
927
+ type: "object",
928
+ description: "Report query: {fields?, filter?, sort?, limit?, page?, aggregate?, groupBy?}",
929
+ },
930
+ },
931
+ required: ["name", "collection", "query"],
932
+ },
933
+ description: "Array of stat queries to run across tables",
790
934
  },
791
935
  },
936
+ required: ["stats"],
792
937
  },
793
938
  },
794
939
 
@@ -2101,13 +2246,26 @@ The realtime config is stored in the schema definition and can include specific
2101
2246
 
2102
2247
  // Reports and Analytics Methods
2103
2248
  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}`);
2249
+ const { collection, fields, filter, sort, limit, page, aggregate, groupBy } = args;
2250
+ const body = {};
2251
+ if (fields) body.fields = fields;
2252
+ if (filter) body.filter = filter;
2253
+ if (sort) {
2254
+ // Convert "field:desc" → "-field", "field:asc" → "field" for ItemsService
2255
+ body.sort = sort.map(s => {
2256
+ const [field, dir] = s.split(':');
2257
+ return dir?.toLowerCase() === 'desc' ? `-${field}` : field;
2258
+ });
2259
+ }
2260
+ if (limit !== undefined) body.limit = limit;
2261
+ if (page !== undefined) body.page = page;
2262
+ if (aggregate) body.aggregate = aggregate;
2263
+ if (groupBy) body.groupBy = groupBy;
2264
+
2265
+ const report = await baasixRequest(`/reports/${encodeURIComponent(collection)}`, {
2266
+ method: 'POST',
2267
+ data: body,
2268
+ });
2111
2269
  return {
2112
2270
  content: [
2113
2271
  {
@@ -2119,17 +2277,16 @@ The realtime config is stored in the schema definition and can include specific
2119
2277
  }
2120
2278
 
2121
2279
  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}`);
2280
+ const { stats } = args;
2281
+ const result = await baasixRequest(`/reports/stats`, {
2282
+ method: 'POST',
2283
+ data: { stats },
2284
+ });
2128
2285
  return {
2129
2286
  content: [
2130
2287
  {
2131
2288
  type: "text",
2132
- text: JSON.stringify(stats, null, 2),
2289
+ text: JSON.stringify(result, null, 2),
2133
2290
  },
2134
2291
  ],
2135
2292
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baasix/mcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Model Context Protocol (MCP) server for Baasix Backend-as-a-Service - provides 40+ tools for schema management, CRUD operations with 50+ filter operators, relationships, permissions, files, workflows, and more",
5
5
  "type": "module",
6
6
  "main": "baasix/index.js",