@centrali-io/centrali-mcp 3.1.4 → 3.1.5

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.
@@ -2,6 +2,27 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { CentraliSDK } from "@centrali-io/centrali-sdk";
3
3
  import { z } from "zod";
4
4
 
5
+ function formatError(error: unknown, context: string): string {
6
+ if (error && typeof error === 'object') {
7
+ const e = error as Record<string, any>;
8
+ if ('message' in e) {
9
+ let msg = `Error ${context}`;
10
+ if ('code' in e || 'status' in e) {
11
+ msg += `: [${e.code ?? e.status ?? 'ERROR'}] ${e.message}`;
12
+ } else {
13
+ msg += `: ${e.message}`;
14
+ }
15
+ if (Array.isArray(e.fieldErrors) && e.fieldErrors.length > 0) {
16
+ msg += '\nField errors:\n' + (e.fieldErrors as Array<{field: string; message: string}>)
17
+ .map(f => ` ${f.field}: ${f.message}`)
18
+ .join('\n');
19
+ }
20
+ return msg;
21
+ }
22
+ }
23
+ return `Error ${context}: ${error instanceof Error ? error.message : String(error)}`;
24
+ }
25
+
5
26
  export function registerSearchTools(server: McpServer, sdk: CentraliSDK) {
6
27
  server.tool(
7
28
  "search_records",
@@ -34,12 +55,12 @@ export function registerSearchTools(server: McpServer, sdk: CentraliSDK) {
34
55
  { type: "text", text: JSON.stringify(result.data, null, 2) },
35
56
  ],
36
57
  };
37
- } catch (error: any) {
58
+ } catch (error: unknown) {
38
59
  return {
39
60
  content: [
40
61
  {
41
62
  type: "text",
42
- text: `Error searching records: ${error.message}`,
63
+ text: formatError(error, "searching records"),
43
64
  },
44
65
  ],
45
66
  isError: true,
@@ -2,6 +2,27 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { CentraliSDK } from "@centrali-io/centrali-sdk";
3
3
  import { z } from "zod";
4
4
 
5
+ function formatError(error: unknown, context: string): string {
6
+ if (error && typeof error === 'object') {
7
+ const e = error as Record<string, any>;
8
+ if ('message' in e) {
9
+ let msg = `Error ${context}`;
10
+ if ('code' in e || 'status' in e) {
11
+ msg += `: [${e.code ?? e.status ?? 'ERROR'}] ${e.message}`;
12
+ } else {
13
+ msg += `: ${e.message}`;
14
+ }
15
+ if (Array.isArray(e.fieldErrors) && e.fieldErrors.length > 0) {
16
+ msg += '\nField errors:\n' + (e.fieldErrors as Array<{field: string; message: string}>)
17
+ .map(f => ` ${f.field}: ${f.message}`)
18
+ .join('\n');
19
+ }
20
+ return msg;
21
+ }
22
+ }
23
+ return `Error ${context}: ${error instanceof Error ? error.message : String(error)}`;
24
+ }
25
+
5
26
  export function registerSmartQueryTools(server: McpServer, sdk: CentraliSDK) {
6
27
  server.tool(
7
28
  "list_smart_queries",
@@ -24,12 +45,12 @@ export function registerSmartQueryTools(server: McpServer, sdk: CentraliSDK) {
24
45
  { type: "text", text: JSON.stringify(result.data, null, 2) },
25
46
  ],
26
47
  };
27
- } catch (error: any) {
48
+ } catch (error: unknown) {
28
49
  return {
29
50
  content: [
30
51
  {
31
52
  type: "text",
32
- text: `Error listing smart queries: ${error.message}`,
53
+ text: formatError(error, "listing smart queries"),
33
54
  },
34
55
  ],
35
56
  isError: true,
@@ -66,12 +87,12 @@ export function registerSmartQueryTools(server: McpServer, sdk: CentraliSDK) {
66
87
  { type: "text", text: JSON.stringify(result.data, null, 2) },
67
88
  ],
68
89
  };
69
- } catch (error: any) {
90
+ } catch (error: unknown) {
70
91
  return {
71
92
  content: [
72
93
  {
73
94
  type: "text",
74
- text: `Error executing smart query '${queryId}': ${error.message}`,
95
+ text: formatError(error, `executing smart query '${queryId}'`),
75
96
  },
76
97
  ],
77
98
  isError: true,
@@ -2,6 +2,27 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { CentraliSDK } from "@centrali-io/centrali-sdk";
3
3
  import { z } from "zod";
4
4
 
5
+ function formatError(error: unknown, context: string): string {
6
+ if (error && typeof error === 'object') {
7
+ const e = error as Record<string, any>;
8
+ if ('message' in e) {
9
+ let msg = `Error ${context}`;
10
+ if ('code' in e || 'status' in e) {
11
+ msg += `: [${e.code ?? e.status ?? 'ERROR'}] ${e.message}`;
12
+ } else {
13
+ msg += `: ${e.message}`;
14
+ }
15
+ if (Array.isArray(e.fieldErrors) && e.fieldErrors.length > 0) {
16
+ msg += '\nField errors:\n' + (e.fieldErrors as Array<{field: string; message: string}>)
17
+ .map(f => ` ${f.field}: ${f.message}`)
18
+ .join('\n');
19
+ }
20
+ return msg;
21
+ }
22
+ }
23
+ return `Error ${context}: ${error instanceof Error ? error.message : String(error)}`;
24
+ }
25
+
5
26
  export function registerStructureTools(server: McpServer, sdk: CentraliSDK) {
6
27
  server.tool(
7
28
  "list_structures",
@@ -18,12 +39,12 @@ export function registerStructureTools(server: McpServer, sdk: CentraliSDK) {
18
39
  { type: "text", text: JSON.stringify(result.data, null, 2) },
19
40
  ],
20
41
  };
21
- } catch (error: any) {
42
+ } catch (error: unknown) {
22
43
  return {
23
44
  content: [
24
45
  {
25
46
  type: "text",
26
- text: `Error listing structures: ${error.message}`,
47
+ text: formatError(error, "listing structures"),
27
48
  },
28
49
  ],
29
50
  isError: true,
@@ -48,12 +69,12 @@ export function registerStructureTools(server: McpServer, sdk: CentraliSDK) {
48
69
  { type: "text", text: JSON.stringify(result.data, null, 2) },
49
70
  ],
50
71
  };
51
- } catch (error: any) {
72
+ } catch (error: unknown) {
52
73
  return {
53
74
  content: [
54
75
  {
55
76
  type: "text",
56
- text: `Error getting structure '${recordSlug}': ${error.message}`,
77
+ text: formatError(error, `getting structure '${recordSlug}'`),
57
78
  },
58
79
  ],
59
80
  isError: true,
@@ -0,0 +1,193 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CentraliSDK, ListValidationSuggestionsOptions } from "@centrali-io/centrali-sdk";
3
+ import { z } from "zod";
4
+
5
+ function formatError(error: unknown, context: string): string {
6
+ if (error && typeof error === "object") {
7
+ const e = error as Record<string, any>;
8
+ if ("message" in e) {
9
+ let msg = `Error ${context}`;
10
+ if ("code" in e || "status" in e) {
11
+ msg += `: [${e.code ?? e.status ?? "ERROR"}] ${e.message}`;
12
+ } else {
13
+ msg += `: ${e.message}`;
14
+ }
15
+ if (Array.isArray(e.fieldErrors) && e.fieldErrors.length > 0) {
16
+ msg +=
17
+ "\nField errors:\n" +
18
+ (e.fieldErrors as Array<{ field: string; message: string }>)
19
+ .map((f) => ` ${f.field}: ${f.message}`)
20
+ .join("\n");
21
+ }
22
+ return msg;
23
+ }
24
+ }
25
+ return `Error ${context}: ${error instanceof Error ? error.message : String(error)}`;
26
+ }
27
+
28
+ export function registerValidationTools(server: McpServer, sdk: CentraliSDK) {
29
+ server.tool(
30
+ "trigger_validation_scan",
31
+ "Trigger an AI-powered data quality scan on a structure. Detects typos, format inconsistencies, duplicates, and other data issues. Returns a batchId — use get_validation_summary or list_validation_suggestions to see results after the scan completes.",
32
+ {
33
+ structureSlug: z.string().describe("The structure's record slug to scan"),
34
+ validationTypes: z
35
+ .array(z.enum(["typo", "format", "duplicate", "semantic", "type"]))
36
+ .optional()
37
+ .describe("Specific validation types to run. If omitted, all types are run."),
38
+ },
39
+ async ({ structureSlug, validationTypes }) => {
40
+ try {
41
+ const options = validationTypes ? { validationTypes } : undefined;
42
+ const result = await sdk.validation.triggerScan(structureSlug, options);
43
+ return {
44
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
45
+ };
46
+ } catch (error: unknown) {
47
+ return {
48
+ content: [
49
+ {
50
+ type: "text",
51
+ text: formatError(
52
+ error,
53
+ `triggering validation scan for '${structureSlug}'`
54
+ ),
55
+ },
56
+ ],
57
+ isError: true,
58
+ };
59
+ }
60
+ }
61
+ );
62
+
63
+ server.tool(
64
+ "list_validation_suggestions",
65
+ "List data quality suggestions generated by validation scans. Each suggestion identifies an issue in a record and proposes a fix.",
66
+ {
67
+ structureSlug: z
68
+ .string()
69
+ .optional()
70
+ .describe("Filter suggestions to a specific structure"),
71
+ status: z
72
+ .enum(["pending", "accepted", "rejected", "auto-applied"])
73
+ .optional()
74
+ .describe("Filter by suggestion status (default: all)"),
75
+ issueType: z
76
+ .enum(["typo", "format", "duplicate", "semantic", "type"])
77
+ .optional()
78
+ .describe("Filter by issue type"),
79
+ minConfidence: z
80
+ .number()
81
+ .optional()
82
+ .describe(
83
+ "Minimum confidence score (0-1). Use 0.9 for high-confidence suggestions only."
84
+ ),
85
+ },
86
+ async ({ structureSlug, status, issueType, minConfidence }) => {
87
+ try {
88
+ const options: ListValidationSuggestionsOptions = {};
89
+ if (structureSlug) options.structureSlug = structureSlug;
90
+ if (status) options.status = status;
91
+ if (issueType) options.issueType = issueType;
92
+ if (minConfidence !== undefined) options.minConfidence = minConfidence;
93
+
94
+ const result = await sdk.validation.listSuggestions(
95
+ Object.keys(options).length > 0 ? options : undefined
96
+ );
97
+ return {
98
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
99
+ };
100
+ } catch (error: unknown) {
101
+ return {
102
+ content: [
103
+ {
104
+ type: "text",
105
+ text: formatError(error, "listing validation suggestions"),
106
+ },
107
+ ],
108
+ isError: true,
109
+ };
110
+ }
111
+ }
112
+ );
113
+
114
+ server.tool(
115
+ "get_validation_summary",
116
+ "Get a summary of data quality across the workspace — counts of pending, accepted, and rejected suggestions. Optionally filter by structure.",
117
+ {
118
+ structureSlug: z
119
+ .string()
120
+ .optional()
121
+ .describe(
122
+ "Filter summary to a specific structure. If omitted, returns workspace-wide summary."
123
+ ),
124
+ },
125
+ async ({ structureSlug }) => {
126
+ try {
127
+ const result = await sdk.validation.getSummary(structureSlug);
128
+ return {
129
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
130
+ };
131
+ } catch (error: unknown) {
132
+ return {
133
+ content: [
134
+ { type: "text", text: formatError(error, "getting validation summary") },
135
+ ],
136
+ isError: true,
137
+ };
138
+ }
139
+ }
140
+ );
141
+
142
+ server.tool(
143
+ "accept_validation_suggestion",
144
+ "Accept a data quality suggestion and apply the fix to the record. The suggested correction is written to the record automatically.",
145
+ {
146
+ suggestionId: z.string().describe("The suggestion ID (UUID) to accept"),
147
+ },
148
+ async ({ suggestionId }) => {
149
+ try {
150
+ const result = await sdk.validation.accept(suggestionId);
151
+ return {
152
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
153
+ };
154
+ } catch (error: unknown) {
155
+ return {
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: formatError(error, `accepting suggestion '${suggestionId}'`),
160
+ },
161
+ ],
162
+ isError: true,
163
+ };
164
+ }
165
+ }
166
+ );
167
+
168
+ server.tool(
169
+ "reject_validation_suggestion",
170
+ "Reject a data quality suggestion, marking it as incorrect or not applicable. The record is left unchanged.",
171
+ {
172
+ suggestionId: z.string().describe("The suggestion ID (UUID) to reject"),
173
+ },
174
+ async ({ suggestionId }) => {
175
+ try {
176
+ const result = await sdk.validation.reject(suggestionId);
177
+ return {
178
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
179
+ };
180
+ } catch (error: unknown) {
181
+ return {
182
+ content: [
183
+ {
184
+ type: "text",
185
+ text: formatError(error, `rejecting suggestion '${suggestionId}'`),
186
+ },
187
+ ],
188
+ isError: true,
189
+ };
190
+ }
191
+ }
192
+ );
193
+ }