@centrali-io/centrali-mcp 5.4.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/README.md
CHANGED
|
@@ -115,7 +115,7 @@ After connecting, call `describe_centrali` first — it returns the full capabil
|
|
|
115
115
|
### Records
|
|
116
116
|
| Tool | Description |
|
|
117
117
|
|------|-------------|
|
|
118
|
-
| `query_records` | Query records
|
|
118
|
+
| `query_records` | Query records using the canonical Centrali query language (POST /records/query). Accepts a `QueryDefinition` body (`where`, `text`, `sort`, `page`, `select`). |
|
|
119
119
|
| `get_record` | Get a single record by ID |
|
|
120
120
|
| `get_records_by_ids` | Get multiple records by IDs |
|
|
121
121
|
| `create_record` | Create a new record |
|
|
@@ -154,16 +154,19 @@ After connecting, call `describe_centrali` first — it returns the full capabil
|
|
|
154
154
|
| `invoke_endpoint` | Call a sync compute endpoint by path (returns response inline) |
|
|
155
155
|
| `remove_allowed_domain` | Remove a domain from the allowlist |
|
|
156
156
|
|
|
157
|
-
### Smart Queries
|
|
157
|
+
### Saved Queries (a.k.a. Smart Queries)
|
|
158
|
+
|
|
159
|
+
Saved queries store a canonical `QueryDefinition` plus optional variables. Tool names keep the `_smart_query` suffix for backwards compatibility, but inputs and outputs are the canonical shape.
|
|
160
|
+
|
|
158
161
|
| Tool | Description |
|
|
159
162
|
|------|-------------|
|
|
160
|
-
| `list_smart_queries` | List
|
|
161
|
-
| `get_smart_query` | Get a
|
|
162
|
-
| `create_smart_query` | Create a reusable parameterized query |
|
|
163
|
-
| `update_smart_query` | Update a
|
|
164
|
-
| `delete_smart_query` | Delete a
|
|
165
|
-
| `execute_smart_query` | Execute a
|
|
166
|
-
| `test_smart_query` | Test a query definition without saving |
|
|
163
|
+
| `list_smart_queries` | List saved queries, optionally by collection |
|
|
164
|
+
| `get_smart_query` | Get a saved query by ID |
|
|
165
|
+
| `create_smart_query` | Create a reusable parameterized query (canonical body) |
|
|
166
|
+
| `update_smart_query` | Update a saved query (canonical body) |
|
|
167
|
+
| `delete_smart_query` | Delete a saved query |
|
|
168
|
+
| `execute_smart_query` | Execute a saved query with optional variables |
|
|
169
|
+
| `test_smart_query` | Test a canonical query definition without saving |
|
|
167
170
|
|
|
168
171
|
### Orchestrations
|
|
169
172
|
| Tool | Description |
|
|
@@ -19,6 +19,15 @@ export type ToolResult = {
|
|
|
19
19
|
*
|
|
20
20
|
* Call sites annotate the arg shape explicitly via the `Args` generic so the
|
|
21
21
|
* handler body stays fully type-safe.
|
|
22
|
+
*
|
|
23
|
+
* CEN-1099 / CEN-1186 — re-investigated when query tool inputs were tightened
|
|
24
|
+
* to the canonical `QueryDefinition` shape (small, finite). Direct
|
|
25
|
+
* `server.tool(...)` still trips `TS2589 Type instantiation is excessively
|
|
26
|
+
* deep and possibly infinite` against the smaller schema, so this wrapper
|
|
27
|
+
* stays. Repro:
|
|
28
|
+
* import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
29
|
+
* server.tool("x", "x", { resource: z.string() }, async (a) => ({ content: [] }));
|
|
30
|
+
* // → TS2589 from `ShapeOutput` over `ZodRawShape`.
|
|
22
31
|
*/
|
|
23
32
|
export declare function registerTool<Args>(server: McpServer, name: string, description: string, schema: ZodRawShape, handler: (args: Args) => Promise<ToolResult>): void;
|
|
24
33
|
export declare function formatError(error: unknown, context: string): string;
|
package/dist/tools/_register.js
CHANGED
|
@@ -14,6 +14,15 @@ exports.formatError = formatError;
|
|
|
14
14
|
*
|
|
15
15
|
* Call sites annotate the arg shape explicitly via the `Args` generic so the
|
|
16
16
|
* handler body stays fully type-safe.
|
|
17
|
+
*
|
|
18
|
+
* CEN-1099 / CEN-1186 — re-investigated when query tool inputs were tightened
|
|
19
|
+
* to the canonical `QueryDefinition` shape (small, finite). Direct
|
|
20
|
+
* `server.tool(...)` still trips `TS2589 Type instantiation is excessively
|
|
21
|
+
* deep and possibly infinite` against the smaller schema, so this wrapper
|
|
22
|
+
* stays. Repro:
|
|
23
|
+
* import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24
|
+
* server.tool("x", "x", { resource: z.string() }, async (a) => ({ content: [] }));
|
|
25
|
+
* // → TS2589 from `ShapeOutput` over `ZodRawShape`.
|
|
17
26
|
*/
|
|
18
27
|
function registerTool(server, name, description, schema, handler) {
|
|
19
28
|
server.tool(name, description, schema, handler);
|
package/dist/tools/describe.js
CHANGED
|
@@ -585,14 +585,14 @@ function registerDescribeTools(server) {
|
|
|
585
585
|
});
|
|
586
586
|
}));
|
|
587
587
|
// ── Records ────────────────────────────────────────────────────────
|
|
588
|
-
(0, _register_js_1.registerTool)(server, "describe_records", "Get the schema reference for Centrali record operations. Explains
|
|
588
|
+
(0, _register_js_1.registerTool)(server, "describe_records", "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.", {}, () => __awaiter(this, void 0, void 0, function* () {
|
|
589
589
|
return ({
|
|
590
590
|
content: [
|
|
591
591
|
{
|
|
592
592
|
type: "text",
|
|
593
593
|
text: JSON.stringify({
|
|
594
594
|
domain: "Records",
|
|
595
|
-
description: "Records are rows of data stored in collections. All custom field values live under the 'data' namespace.",
|
|
595
|
+
description: "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.",
|
|
596
596
|
record_shape: {
|
|
597
597
|
id: "UUID — auto-generated",
|
|
598
598
|
collectionId: "UUID — the collection this record belongs to",
|
|
@@ -603,71 +603,82 @@ function registerDescribeTools(server) {
|
|
|
603
603
|
updatedBy: "UUID — user who last updated",
|
|
604
604
|
isDeleted: "boolean — true if soft-deleted",
|
|
605
605
|
},
|
|
606
|
-
|
|
607
|
-
description: "
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
"
|
|
611
|
-
"
|
|
612
|
-
"
|
|
613
|
-
"
|
|
614
|
-
"
|
|
615
|
-
"
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
"
|
|
622
|
-
"
|
|
623
|
-
"
|
|
624
|
-
"
|
|
625
|
-
"
|
|
626
|
-
"
|
|
627
|
-
"
|
|
628
|
-
"
|
|
629
|
-
"
|
|
630
|
-
"
|
|
606
|
+
query_definition: {
|
|
607
|
+
description: "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).",
|
|
608
|
+
shape: {
|
|
609
|
+
resource: "string — collection slug",
|
|
610
|
+
where: "WhereExpression — FieldConditionMap | { and: [...] } | { or: [...] } | { not: ... }",
|
|
611
|
+
text: "{ query: string, fields?: string[], typoTolerance?: boolean }",
|
|
612
|
+
sort: "[{ field: string, direction: 'asc' | 'desc' }]",
|
|
613
|
+
page: "{ limit: number, offset?: number } | { limit: number, cursor?: string }",
|
|
614
|
+
select: "{ fields: string[] }",
|
|
615
|
+
include: "[{ relation: string }] — relation expansion (collection-defined relations)",
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
operators: {
|
|
619
|
+
description: "Field operators. Use exactly one per FieldCondition.",
|
|
620
|
+
list: [
|
|
621
|
+
"eq — equal",
|
|
622
|
+
"ne — not equal",
|
|
623
|
+
"gt — greater than",
|
|
624
|
+
"gte — greater than or equal",
|
|
625
|
+
"lt — less than",
|
|
626
|
+
"lte — less than or equal",
|
|
627
|
+
"in — value in array",
|
|
628
|
+
"nin — value not in array",
|
|
629
|
+
"contains — substring match",
|
|
630
|
+
"startsWith — prefix match",
|
|
631
|
+
"endsWith — suffix match",
|
|
632
|
+
"hasAny — array field has any of these",
|
|
633
|
+
"hasAll — array field has all of these",
|
|
634
|
+
"exists — boolean: field is set",
|
|
631
635
|
],
|
|
636
|
+
boolean_tree: ["and", "or", "not"],
|
|
632
637
|
},
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
638
|
+
examples: {
|
|
639
|
+
"Exact match": { "data.status": { eq: "active" } },
|
|
640
|
+
"Greater than": { "data.price": { gt: 100 } },
|
|
641
|
+
"Boolean tree": {
|
|
642
|
+
and: [
|
|
643
|
+
{ "data.status": { eq: "open" } },
|
|
644
|
+
{ or: [{ "data.amount": { gte: 100 } }, { "data.priority": { eq: "high" } }] },
|
|
645
|
+
],
|
|
640
646
|
},
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
},
|
|
647
|
-
expand: {
|
|
648
|
-
description: "Comma-separated list of reference field names to expand (join). Returns the full referenced record instead of just the ID.",
|
|
649
|
-
example: "expand: 'customer,product'",
|
|
650
|
-
},
|
|
651
|
-
dateWindow: {
|
|
652
|
-
description: "Date range filter. Restricts results to records where a date field falls within a given range. Both 'from' and 'to' are optional (open-ended ranges allowed).",
|
|
653
|
-
shape: {
|
|
654
|
-
field: "string — date field to filter on (e.g., 'createdAt', 'updatedAt', or a custom date field)",
|
|
655
|
-
from: "string? — ISO 8601 lower bound (inclusive)",
|
|
656
|
-
to: "string? — ISO 8601 upper bound (inclusive)",
|
|
647
|
+
"Date window": {
|
|
648
|
+
and: [
|
|
649
|
+
{ createdAt: { gte: "2024-03-01T00:00:00Z" } },
|
|
650
|
+
{ createdAt: { lte: "2024-03-31T23:59:59Z" } },
|
|
651
|
+
],
|
|
657
652
|
},
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
653
|
+
"Sort + page": {
|
|
654
|
+
sort: [{ field: "createdAt", direction: "desc" }],
|
|
655
|
+
page: { limit: 50 },
|
|
661
656
|
},
|
|
657
|
+
"Projection": { select: { fields: ["id", "data.status", "data.amount"] } },
|
|
658
|
+
"Full-text search": { text: { query: "urgent shipping", fields: ["data.notes"] } },
|
|
662
659
|
},
|
|
663
|
-
|
|
664
|
-
description: "
|
|
660
|
+
response_envelope: {
|
|
661
|
+
description: "All canonical queries return { data, meta }.",
|
|
662
|
+
shape: {
|
|
663
|
+
data: "T[]",
|
|
664
|
+
meta: {
|
|
665
|
+
limit: "number",
|
|
666
|
+
offset: "number? (offset mode)",
|
|
667
|
+
cursor: "string? (cursor mode)",
|
|
668
|
+
nextCursor: "string?",
|
|
669
|
+
hasMore: "boolean?",
|
|
670
|
+
total: "number? (when computable; depends on executor)",
|
|
671
|
+
processingTimeMs: "number?",
|
|
672
|
+
mode: "'filter' | 'search' | 'hybrid'",
|
|
673
|
+
},
|
|
674
|
+
},
|
|
665
675
|
},
|
|
666
|
-
|
|
667
|
-
|
|
676
|
+
page_defaults: {
|
|
677
|
+
limit_default: 50,
|
|
678
|
+
limit_max: 500,
|
|
668
679
|
},
|
|
669
680
|
upsert: {
|
|
670
|
-
description: "Atomic create-or-update. Provide 'match' fields to find existing record and 'data' for the full record body.",
|
|
681
|
+
description: "Atomic create-or-update via upsert_record. Provide 'match' fields to find an existing record and 'data' for the full record body.",
|
|
671
682
|
example: {
|
|
672
683
|
match: { sku: "WIDGET-001" },
|
|
673
684
|
data: { sku: "WIDGET-001", name: "Widget", price: 9.99 },
|
|
@@ -677,6 +688,7 @@ function registerDescribeTools(server) {
|
|
|
677
688
|
tips: [
|
|
678
689
|
"Always use the 'data.' prefix when filtering on custom fields",
|
|
679
690
|
"System fields (id, createdAt, updatedAt) don't need the 'data.' prefix",
|
|
691
|
+
"Each FieldCondition must use exactly one operator — '{ eq: 1, ne: 2 }' is rejected",
|
|
680
692
|
"Use upsert_record for idempotent imports — it won't create duplicates",
|
|
681
693
|
"Soft-deleted records can be restored with restore_record",
|
|
682
694
|
"Use get_records_by_ids for batch lookups (more efficient than individual get_record calls)",
|
|
@@ -974,73 +986,77 @@ function registerDescribeTools(server) {
|
|
|
974
986
|
});
|
|
975
987
|
}));
|
|
976
988
|
// ── Smart Queries ──────────────────────────────────────────────────
|
|
977
|
-
(0, _register_js_1.registerTool)(server, "describe_smart_queries", "Get the schema reference for Centrali smart queries. Explains
|
|
989
|
+
(0, _register_js_1.registerTool)(server, "describe_smart_queries", "Get the schema reference for Centrali saved (a.k.a. smart) queries. Explains the canonical QueryDefinition body, variable substitution, and execution.", {}, () => __awaiter(this, void 0, void 0, function* () {
|
|
978
990
|
return ({
|
|
979
991
|
content: [
|
|
980
992
|
{
|
|
981
993
|
type: "text",
|
|
982
994
|
text: JSON.stringify({
|
|
983
|
-
domain: "Smart Queries",
|
|
984
|
-
description: "
|
|
985
|
-
|
|
995
|
+
domain: "Saved Queries (a.k.a. Smart Queries)",
|
|
996
|
+
description: "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.",
|
|
997
|
+
saved_query_shape: {
|
|
986
998
|
id: "UUID",
|
|
987
999
|
name: "string — display name",
|
|
988
1000
|
recordSlug: "string — the collection this query targets",
|
|
989
1001
|
description: "string | null",
|
|
990
|
-
|
|
991
|
-
variables: "object[] — declared variables with name, type, and default values",
|
|
1002
|
+
queryDefinition: "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.",
|
|
992
1003
|
},
|
|
1004
|
+
query_definition_reference: "Use describe_records for the full QueryDefinition shape, operator vocabulary, and examples.",
|
|
993
1005
|
variable_syntax: {
|
|
994
|
-
description: "Variables use double-brace syntax: {{variableName}}.
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
status: "active",
|
|
1002
|
-
minAmount: "1000",
|
|
1006
|
+
description: "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.",
|
|
1007
|
+
example_query_definition: {
|
|
1008
|
+
where: {
|
|
1009
|
+
and: [
|
|
1010
|
+
{ "data.status": { eq: "{{statusFilter}}" } },
|
|
1011
|
+
{ "data.amount": { gte: "{{minAmount}}" } },
|
|
1012
|
+
],
|
|
1003
1013
|
},
|
|
1014
|
+
sort: [{ field: "createdAt", direction: "desc" }],
|
|
1015
|
+
page: { limit: 100 },
|
|
1016
|
+
},
|
|
1017
|
+
example_execution_variables: {
|
|
1018
|
+
statusFilter: "active",
|
|
1019
|
+
minAmount: 1000,
|
|
1004
1020
|
},
|
|
1005
|
-
note: "
|
|
1021
|
+
note: "Pass values via the 'variables' arg on execute_smart_query / test_smart_query. Values are substituted before the engine validates the query.",
|
|
1006
1022
|
},
|
|
1007
1023
|
crud_tools: {
|
|
1008
1024
|
get_smart_query: {
|
|
1009
|
-
description: "Get a
|
|
1025
|
+
description: "Get a saved query by ID, including its full canonical definition",
|
|
1010
1026
|
required_params: ["recordSlug", "queryId"],
|
|
1011
1027
|
},
|
|
1012
1028
|
create_smart_query: {
|
|
1013
|
-
description: "Create a new
|
|
1029
|
+
description: "Create a new saved query for a collection",
|
|
1014
1030
|
required_params: ["recordSlug", "name", "queryDefinition"],
|
|
1015
1031
|
optional_params: ["description"],
|
|
1016
1032
|
queryDefinition_example: {
|
|
1017
|
-
where: { status: {
|
|
1033
|
+
where: { "data.status": { eq: "active" } },
|
|
1018
1034
|
sort: [{ field: "createdAt", direction: "desc" }],
|
|
1019
|
-
limit: 100,
|
|
1035
|
+
page: { limit: 100 },
|
|
1020
1036
|
},
|
|
1021
1037
|
},
|
|
1022
1038
|
update_smart_query: {
|
|
1023
|
-
description: "Update an existing
|
|
1039
|
+
description: "Update an existing saved query. Partial updates supported.",
|
|
1024
1040
|
required_params: ["recordSlug", "queryId"],
|
|
1025
1041
|
optional_params: ["name", "description", "queryDefinition"],
|
|
1026
1042
|
},
|
|
1027
1043
|
delete_smart_query: {
|
|
1028
|
-
description: "Delete a
|
|
1044
|
+
description: "Delete a saved query by ID",
|
|
1029
1045
|
required_params: ["recordSlug", "queryId"],
|
|
1030
1046
|
},
|
|
1031
1047
|
test_smart_query: {
|
|
1032
|
-
description: "Test execute a query definition without saving it. Preview results before creating.",
|
|
1048
|
+
description: "Test execute a canonical query definition without saving it. Preview results before creating.",
|
|
1033
1049
|
required_params: ["recordSlug", "queryDefinition"],
|
|
1034
1050
|
optional_params: ["variables"],
|
|
1035
1051
|
},
|
|
1036
1052
|
},
|
|
1037
1053
|
tips: [
|
|
1038
1054
|
"Use list_smart_queries to discover available queries (optionally filter by collection slug)",
|
|
1039
|
-
"
|
|
1040
|
-
"Variables are
|
|
1041
|
-
"Prefer
|
|
1042
|
-
"Use test_smart_query to validate
|
|
1043
|
-
"
|
|
1055
|
+
"Saved queries return the same canonical { data, meta } envelope as query_records",
|
|
1056
|
+
"Variables are declared when creating the query — read the query's 'variables' field to see what's expected",
|
|
1057
|
+
"Prefer saved queries over ad-hoc query_records filters for reusable logic",
|
|
1058
|
+
"Use test_smart_query to validate canonical syntax and preview results before saving",
|
|
1059
|
+
"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', …)",
|
|
1044
1060
|
],
|
|
1045
1061
|
}, null, 2),
|
|
1046
1062
|
},
|
package/dist/tools/records.d.ts
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { CentraliSDK } from "@centrali-io/centrali-sdk";
|
|
3
|
+
declare function parseLegacyFilters(filters: unknown): unknown;
|
|
4
|
+
declare function parseLegacySort(sort: string): Array<{
|
|
5
|
+
field: string;
|
|
6
|
+
direction: "asc" | "desc";
|
|
7
|
+
}>;
|
|
8
|
+
type QueryRecordsArgs = {
|
|
9
|
+
resource?: string;
|
|
10
|
+
recordSlug?: string;
|
|
11
|
+
where?: unknown;
|
|
12
|
+
text?: unknown;
|
|
13
|
+
sort?: unknown;
|
|
14
|
+
page?: unknown;
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
select?: unknown;
|
|
17
|
+
include?: unknown;
|
|
18
|
+
filters?: Record<string, unknown>;
|
|
19
|
+
expand?: string;
|
|
20
|
+
dateWindow?: {
|
|
21
|
+
field: string;
|
|
22
|
+
from?: string;
|
|
23
|
+
to?: string;
|
|
24
|
+
};
|
|
25
|
+
includeDeleted?: boolean;
|
|
26
|
+
includeTotal?: boolean;
|
|
27
|
+
};
|
|
28
|
+
declare function translateQueryRecordsArgs(args: QueryRecordsArgs): {
|
|
29
|
+
resource: string;
|
|
30
|
+
definition: Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
export declare const _internal: {
|
|
33
|
+
translateQueryRecordsArgs: typeof translateQueryRecordsArgs;
|
|
34
|
+
parseLegacyFilters: typeof parseLegacyFilters;
|
|
35
|
+
parseLegacySort: typeof parseLegacySort;
|
|
36
|
+
};
|
|
3
37
|
export declare function registerRecordTools(server: McpServer, sdk: CentraliSDK, centraliUrl: string, workspaceId: string): void;
|
|
38
|
+
export {};
|