@centrali-io/centrali-mcp 5.3.0 → 5.5.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 +12 -9
- package/dist/tools/_register.d.ts +9 -0
- package/dist/tools/_register.js +9 -0
- package/dist/tools/describe.js +102 -86
- package/dist/tools/records.d.ts +35 -0
- package/dist/tools/records.js +226 -30
- package/dist/tools/smart-queries.js +58 -36
- package/package.json +3 -2
- package/src/tools/_register.ts +9 -0
- package/src/tools/describe.ts +106 -93
- package/src/tools/records.ts +273 -52
- package/src/tools/smart-queries.ts +70 -43
- package/tests/records.translator.test.cjs +177 -0
package/src/tools/describe.ts
CHANGED
|
@@ -621,9 +621,9 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
621
621
|
|
|
622
622
|
// ── Records ────────────────────────────────────────────────────────
|
|
623
623
|
|
|
624
|
-
registerTool<any>(server,
|
|
624
|
+
registerTool<any>(server,
|
|
625
625
|
"describe_records",
|
|
626
|
-
"Get the schema reference for Centrali record operations. Explains
|
|
626
|
+
"Get the schema reference for Centrali record operations. Explains the canonical QueryDefinition body used by query_records, the operator vocabulary, sort/page/select clauses, and the data. prefix convention.",
|
|
627
627
|
{},
|
|
628
628
|
async () => ({
|
|
629
629
|
content: [
|
|
@@ -633,7 +633,7 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
633
633
|
{
|
|
634
634
|
domain: "Records",
|
|
635
635
|
description:
|
|
636
|
-
"Records are rows of data stored in collections. All custom field values live under the 'data' namespace.",
|
|
636
|
+
"Records are rows of data stored in collections. All custom field values live under the 'data' namespace. The query_records tool accepts a canonical QueryDefinition body; the underlying engine (POST /records/query) is shared with the SDK, console, compute, and Pages.",
|
|
637
637
|
record_shape: {
|
|
638
638
|
id: "UUID — auto-generated",
|
|
639
639
|
collectionId: "UUID — the collection this record belongs to",
|
|
@@ -644,77 +644,85 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
644
644
|
updatedBy: "UUID — user who last updated",
|
|
645
645
|
isDeleted: "boolean — true if soft-deleted",
|
|
646
646
|
},
|
|
647
|
-
|
|
648
|
-
description:
|
|
649
|
-
"Filters are passed as key-value pairs. Keys use the 'data.' prefix for custom fields and bracket notation for operators.",
|
|
650
|
-
examples: {
|
|
651
|
-
"Exact match": { "data.status": "active" },
|
|
652
|
-
"Greater than": { "data.price[gt]": 100 },
|
|
653
|
-
"Less than or equal": { "data.age[lte]": 30 },
|
|
654
|
-
"Not equal": { "data.type[ne]": "draft" },
|
|
655
|
-
"In list": { "data.category[in]": "electronics,books" },
|
|
656
|
-
"Not in list": { "data.status[nin]": "deleted,archived" },
|
|
657
|
-
"Contains (text)": { "data.name[contains]": "john" },
|
|
658
|
-
"Starts with": { "data.email[startswith]": "admin" },
|
|
659
|
-
"Ends with": { "data.domain[endswith]": ".com" },
|
|
660
|
-
},
|
|
661
|
-
operators: [
|
|
662
|
-
"eq (default, no bracket needed)",
|
|
663
|
-
"ne (not equal)",
|
|
664
|
-
"gt (greater than)",
|
|
665
|
-
"gte (greater than or equal)",
|
|
666
|
-
"lt (less than)",
|
|
667
|
-
"lte (less than or equal)",
|
|
668
|
-
"in (comma-separated list)",
|
|
669
|
-
"nin (not in comma-separated list)",
|
|
670
|
-
"contains (substring match)",
|
|
671
|
-
"startswith (prefix match)",
|
|
672
|
-
"endswith (suffix match)",
|
|
673
|
-
],
|
|
674
|
-
},
|
|
675
|
-
sort_syntax: {
|
|
647
|
+
query_definition: {
|
|
676
648
|
description:
|
|
677
|
-
"
|
|
678
|
-
|
|
679
|
-
"
|
|
680
|
-
"
|
|
681
|
-
"
|
|
682
|
-
"
|
|
649
|
+
"Canonical QueryDefinition shape passed to query_records. Field paths are dotted strings ('data.status', 'data.customer.email'). Each FieldCondition uses exactly one operator (no '$' prefix).",
|
|
650
|
+
shape: {
|
|
651
|
+
resource: "string — collection slug",
|
|
652
|
+
where: "WhereExpression — FieldConditionMap | { and: [...] } | { or: [...] } | { not: ... }",
|
|
653
|
+
text: "{ query: string, fields?: string[], typoTolerance?: boolean }",
|
|
654
|
+
sort: "[{ field: string, direction: 'asc' | 'desc' }]",
|
|
655
|
+
page: "{ limit: number, offset?: number } | { limit: number, cursor?: string }",
|
|
656
|
+
select: "{ fields: string[] }",
|
|
657
|
+
include: "[{ relation: string }] — relation expansion (collection-defined relations)",
|
|
683
658
|
},
|
|
684
659
|
},
|
|
685
|
-
|
|
686
|
-
page: "1-indexed page number (default: 1)",
|
|
687
|
-
pageSize: "Records per page (default: 50, max: 500)",
|
|
688
|
-
response_includes:
|
|
689
|
-
"total, page, pageSize, totalPages in the response metadata",
|
|
690
|
-
},
|
|
691
|
-
expand: {
|
|
660
|
+
operators: {
|
|
692
661
|
description:
|
|
693
|
-
"
|
|
694
|
-
|
|
662
|
+
"Field operators. Use exactly one per FieldCondition.",
|
|
663
|
+
list: [
|
|
664
|
+
"eq — equal",
|
|
665
|
+
"ne — not equal",
|
|
666
|
+
"gt — greater than",
|
|
667
|
+
"gte — greater than or equal",
|
|
668
|
+
"lt — less than",
|
|
669
|
+
"lte — less than or equal",
|
|
670
|
+
"in — value in array",
|
|
671
|
+
"nin — value not in array",
|
|
672
|
+
"contains — substring match",
|
|
673
|
+
"startsWith — prefix match",
|
|
674
|
+
"endsWith — suffix match",
|
|
675
|
+
"hasAny — array field has any of these",
|
|
676
|
+
"hasAll — array field has all of these",
|
|
677
|
+
"exists — boolean: field is set",
|
|
678
|
+
],
|
|
679
|
+
boolean_tree: ["and", "or", "not"],
|
|
695
680
|
},
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
681
|
+
examples: {
|
|
682
|
+
"Exact match": { "data.status": { eq: "active" } },
|
|
683
|
+
"Greater than": { "data.price": { gt: 100 } },
|
|
684
|
+
"Boolean tree": {
|
|
685
|
+
and: [
|
|
686
|
+
{ "data.status": { eq: "open" } },
|
|
687
|
+
{ or: [{ "data.amount": { gte: 100 } }, { "data.priority": { eq: "high" } }] },
|
|
688
|
+
],
|
|
703
689
|
},
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
690
|
+
"Date window": {
|
|
691
|
+
and: [
|
|
692
|
+
{ createdAt: { gte: "2024-03-01T00:00:00Z" } },
|
|
693
|
+
{ createdAt: { lte: "2024-03-31T23:59:59Z" } },
|
|
694
|
+
],
|
|
695
|
+
},
|
|
696
|
+
"Sort + page": {
|
|
697
|
+
sort: [{ field: "createdAt", direction: "desc" }],
|
|
698
|
+
page: { limit: 50 },
|
|
707
699
|
},
|
|
700
|
+
"Projection": { select: { fields: ["id", "data.status", "data.amount"] } },
|
|
701
|
+
"Full-text search": { text: { query: "urgent shipping", fields: ["data.notes"] } },
|
|
708
702
|
},
|
|
709
|
-
|
|
710
|
-
description: "
|
|
703
|
+
response_envelope: {
|
|
704
|
+
description: "All canonical queries return { data, meta }.",
|
|
705
|
+
shape: {
|
|
706
|
+
data: "T[]",
|
|
707
|
+
meta: {
|
|
708
|
+
limit: "number",
|
|
709
|
+
offset: "number? (offset mode)",
|
|
710
|
+
cursor: "string? (cursor mode)",
|
|
711
|
+
nextCursor: "string?",
|
|
712
|
+
hasMore: "boolean?",
|
|
713
|
+
total: "number? (when computable; depends on executor)",
|
|
714
|
+
processingTimeMs: "number?",
|
|
715
|
+
mode: "'filter' | 'search' | 'hybrid'",
|
|
716
|
+
},
|
|
717
|
+
},
|
|
711
718
|
},
|
|
712
|
-
|
|
713
|
-
|
|
719
|
+
page_defaults: {
|
|
720
|
+
limit_default: 50,
|
|
721
|
+
limit_max: 500,
|
|
714
722
|
},
|
|
715
723
|
upsert: {
|
|
716
724
|
description:
|
|
717
|
-
"Atomic create-or-update. Provide 'match' fields to find existing record and 'data' for the full record body.",
|
|
725
|
+
"Atomic create-or-update via upsert_record. Provide 'match' fields to find an existing record and 'data' for the full record body.",
|
|
718
726
|
example: {
|
|
719
727
|
match: { sku: "WIDGET-001" },
|
|
720
728
|
data: { sku: "WIDGET-001", name: "Widget", price: 9.99 },
|
|
@@ -724,6 +732,7 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
724
732
|
tips: [
|
|
725
733
|
"Always use the 'data.' prefix when filtering on custom fields",
|
|
726
734
|
"System fields (id, createdAt, updatedAt) don't need the 'data.' prefix",
|
|
735
|
+
"Each FieldCondition must use exactly one operator — '{ eq: 1, ne: 2 }' is rejected",
|
|
727
736
|
"Use upsert_record for idempotent imports — it won't create duplicates",
|
|
728
737
|
"Soft-deleted records can be restored with restore_record",
|
|
729
738
|
"Use get_records_by_ids for batch lookups (more efficient than individual get_record calls)",
|
|
@@ -1054,9 +1063,9 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
1054
1063
|
|
|
1055
1064
|
// ── Smart Queries ──────────────────────────────────────────────────
|
|
1056
1065
|
|
|
1057
|
-
registerTool<any>(server,
|
|
1066
|
+
registerTool<any>(server,
|
|
1058
1067
|
"describe_smart_queries",
|
|
1059
|
-
"Get the schema reference for Centrali smart queries. Explains
|
|
1068
|
+
"Get the schema reference for Centrali saved (a.k.a. smart) queries. Explains the canonical QueryDefinition body, variable substitution, and execution.",
|
|
1060
1069
|
{},
|
|
1061
1070
|
async () => ({
|
|
1062
1071
|
content: [
|
|
@@ -1064,71 +1073,75 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
1064
1073
|
type: "text",
|
|
1065
1074
|
text: JSON.stringify(
|
|
1066
1075
|
{
|
|
1067
|
-
domain: "Smart Queries",
|
|
1076
|
+
domain: "Saved Queries (a.k.a. Smart Queries)",
|
|
1068
1077
|
description:
|
|
1069
|
-
"
|
|
1070
|
-
|
|
1078
|
+
"Saved queries are reusable, parameterized queries defined in the Centrali console. They store a canonical QueryDefinition plus optional variable declarations and are executed under the caller's permissions. Tool names retain the `_smart_query` suffix for backwards compatibility.",
|
|
1079
|
+
saved_query_shape: {
|
|
1071
1080
|
id: "UUID",
|
|
1072
1081
|
name: "string — display name",
|
|
1073
1082
|
recordSlug: "string — the collection this query targets",
|
|
1074
1083
|
description: "string | null",
|
|
1075
|
-
|
|
1076
|
-
"object —
|
|
1077
|
-
variables:
|
|
1078
|
-
"object[] — declared variables with name, type, and default values",
|
|
1084
|
+
queryDefinition:
|
|
1085
|
+
"object — canonical QueryDefinition (without 'resource' — filled in from recordSlug). Variables are referenced as '{{varName}}' placeholders inside operator values; the engine infers them from the body.",
|
|
1079
1086
|
},
|
|
1087
|
+
query_definition_reference:
|
|
1088
|
+
"Use describe_records for the full QueryDefinition shape, operator vocabulary, and examples.",
|
|
1080
1089
|
variable_syntax: {
|
|
1081
1090
|
description:
|
|
1082
|
-
"Variables use double-brace syntax: {{variableName}}.
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
status: "active",
|
|
1090
|
-
minAmount: "1000",
|
|
1091
|
+
"Variables use double-brace syntax: '{{variableName}}'. They appear inside operator values; the engine infers the variable list from the body and resolves substitutions at execution time. There is no separate variable declaration — placeholders in the QueryDefinition are the source of truth.",
|
|
1092
|
+
example_query_definition: {
|
|
1093
|
+
where: {
|
|
1094
|
+
and: [
|
|
1095
|
+
{ "data.status": { eq: "{{statusFilter}}" } },
|
|
1096
|
+
{ "data.amount": { gte: "{{minAmount}}" } },
|
|
1097
|
+
],
|
|
1091
1098
|
},
|
|
1099
|
+
sort: [{ field: "createdAt", direction: "desc" }],
|
|
1100
|
+
page: { limit: 100 },
|
|
1101
|
+
},
|
|
1102
|
+
example_execution_variables: {
|
|
1103
|
+
statusFilter: "active",
|
|
1104
|
+
minAmount: 1000,
|
|
1092
1105
|
},
|
|
1093
|
-
note: "
|
|
1106
|
+
note: "Pass values via the 'variables' arg on execute_smart_query / test_smart_query. Values are substituted before the engine validates the query.",
|
|
1094
1107
|
},
|
|
1095
1108
|
crud_tools: {
|
|
1096
1109
|
get_smart_query: {
|
|
1097
|
-
description: "Get a
|
|
1110
|
+
description: "Get a saved query by ID, including its full canonical definition",
|
|
1098
1111
|
required_params: ["recordSlug", "queryId"],
|
|
1099
1112
|
},
|
|
1100
1113
|
create_smart_query: {
|
|
1101
|
-
description: "Create a new
|
|
1114
|
+
description: "Create a new saved query for a collection",
|
|
1102
1115
|
required_params: ["recordSlug", "name", "queryDefinition"],
|
|
1103
1116
|
optional_params: ["description"],
|
|
1104
1117
|
queryDefinition_example: {
|
|
1105
|
-
where: { status: {
|
|
1118
|
+
where: { "data.status": { eq: "active" } },
|
|
1106
1119
|
sort: [{ field: "createdAt", direction: "desc" }],
|
|
1107
|
-
limit: 100,
|
|
1120
|
+
page: { limit: 100 },
|
|
1108
1121
|
},
|
|
1109
1122
|
},
|
|
1110
1123
|
update_smart_query: {
|
|
1111
|
-
description: "Update an existing
|
|
1124
|
+
description: "Update an existing saved query. Partial updates supported.",
|
|
1112
1125
|
required_params: ["recordSlug", "queryId"],
|
|
1113
1126
|
optional_params: ["name", "description", "queryDefinition"],
|
|
1114
1127
|
},
|
|
1115
1128
|
delete_smart_query: {
|
|
1116
|
-
description: "Delete a
|
|
1129
|
+
description: "Delete a saved query by ID",
|
|
1117
1130
|
required_params: ["recordSlug", "queryId"],
|
|
1118
1131
|
},
|
|
1119
1132
|
test_smart_query: {
|
|
1120
|
-
description: "Test execute a query definition without saving it. Preview results before creating.",
|
|
1133
|
+
description: "Test execute a canonical query definition without saving it. Preview results before creating.",
|
|
1121
1134
|
required_params: ["recordSlug", "queryDefinition"],
|
|
1122
1135
|
optional_params: ["variables"],
|
|
1123
1136
|
},
|
|
1124
1137
|
},
|
|
1125
1138
|
tips: [
|
|
1126
1139
|
"Use list_smart_queries to discover available queries (optionally filter by collection slug)",
|
|
1127
|
-
"
|
|
1128
|
-
"Variables are
|
|
1129
|
-
"Prefer
|
|
1130
|
-
"Use test_smart_query to validate
|
|
1131
|
-
"
|
|
1140
|
+
"Saved queries return the same canonical { data, meta } envelope as query_records",
|
|
1141
|
+
"Variables are declared when creating the query — read the query's 'variables' field to see what's expected",
|
|
1142
|
+
"Prefer saved queries over ad-hoc query_records filters for reusable logic",
|
|
1143
|
+
"Use test_smart_query to validate canonical syntax and preview results before saving",
|
|
1144
|
+
"DO NOT author new saved queries with legacy '$'-prefixed operators ('$eq', '$gte', …). The data service still translates them server-side during the deprecation window, but new saved queries must use canonical operators ('eq', 'gte', …)",
|
|
1132
1145
|
],
|
|
1133
1146
|
},
|
|
1134
1147
|
null,
|