@kynver-app/mcp-estimator 0.2.0 → 0.2.2

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/dist/index.js CHANGED
@@ -5,7 +5,11 @@
5
5
  * Env: KYNVER_API_URL (default https://kynver.com), KYNVER_API_KEY (required for all tools)
6
6
  */
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { handleCliVersionFlag } from "./lib/package-version.js";
8
9
  import { createEstimatorServer } from "./server.js";
10
+ if (handleCliVersionFlag(process.argv.slice(2), import.meta.url, "kynver-mcp-estimator")) {
11
+ // exits
12
+ }
9
13
  const server = createEstimatorServer();
10
14
  const transport = new StdioServerTransport();
11
15
  await server.connect(transport);
@@ -0,0 +1,7 @@
1
+ /** Read a field from the nearest `package.json` above this module. */
2
+ export declare function readOwnPackageJsonField(moduleUrl: string, field: "version" | "name"): string;
3
+ export declare function readOwnPackageVersion(moduleUrl?: string): string;
4
+ export declare function wantsCliVersion(argv: string[]): boolean;
5
+ export declare function printCliVersionAndExit(version: string, binName?: string): never;
6
+ /** When `--version` / `-v` is present, print package version and exit before side effects. */
7
+ export declare function handleCliVersionFlag(argv: string[], moduleUrl?: string, binName?: string): boolean;
@@ -0,0 +1,44 @@
1
+ // @generated — synced from packages/kynver-mcp-sse-lib/src/package-version.ts via scripts/sync-mcp-sse-server.mjs
2
+ // Do not edit by hand; change the canonical file and re-run the sync script.
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ function resolvePackageRoot(moduleUrl) {
7
+ let dir = dirname(fileURLToPath(moduleUrl));
8
+ for (let depth = 0; depth < 6; depth += 1) {
9
+ if (existsSync(join(dir, "package.json")))
10
+ return dir;
11
+ const parent = dirname(dir);
12
+ if (parent === dir)
13
+ break;
14
+ dir = parent;
15
+ }
16
+ throw new Error(`package.json not found above ${dirname(fileURLToPath(moduleUrl))}`);
17
+ }
18
+ /** Read a field from the nearest `package.json` above this module. */
19
+ export function readOwnPackageJsonField(moduleUrl, field) {
20
+ const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
21
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
22
+ const value = pkg[field];
23
+ if (typeof value !== "string" || !value.trim()) {
24
+ throw new Error(`Missing package.json ${field} at ${pkgPath}`);
25
+ }
26
+ return value;
27
+ }
28
+ export function readOwnPackageVersion(moduleUrl = import.meta.url) {
29
+ return readOwnPackageJsonField(moduleUrl, "version");
30
+ }
31
+ export function wantsCliVersion(argv) {
32
+ return argv.some((arg) => arg === "--version" || arg === "-v");
33
+ }
34
+ export function printCliVersionAndExit(version, binName) {
35
+ console.log(binName ? `${binName} ${version}` : version);
36
+ process.exit(0);
37
+ }
38
+ /** When `--version` / `-v` is present, print package version and exit before side effects. */
39
+ export function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
40
+ if (!wantsCliVersion(argv))
41
+ return false;
42
+ printCliVersionAndExit(readOwnPackageVersion(moduleUrl), binName);
43
+ return true;
44
+ }
package/dist/server.js CHANGED
@@ -12,16 +12,19 @@ import * as statsPdfTools from "./tools/stats-pdf-tools.js";
12
12
  import * as visionTools from "./tools/vision-tools.js";
13
13
  import * as companyTools from "./tools/company-tools.js";
14
14
  import * as clientTools from "./tools/client-tools.js";
15
+ import * as takeoffTools from "./tools/takeoff-tools.js";
16
+ import { readOwnPackageVersion } from "./lib/package-version.js";
17
+ const PACKAGE_VERSION = readOwnPackageVersion(import.meta.url);
15
18
  function toolResult(text) {
16
19
  return { content: [{ type: "text", text }] };
17
20
  }
18
21
  export function createEstimatorServer() {
19
- const server = new McpServer({ name: "kynver-mcp-estimator", version: "0.1.0" }, { capabilities: { tools: {} } });
22
+ const server = new McpServer({ name: "kynver-mcp-estimator", version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
20
23
  function register(name, config, run) {
21
24
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
25
  server.registerTool(name, config, async (args) => toolResult(await run(args)));
23
26
  }
24
- // --- Catalog tools (8) ---
27
+ // --- Catalog tools (9) ---
25
28
  register(catalogTools.importCatalog.name, { description: catalogTools.importCatalog.description, inputSchema: catalogTools.importCatalog.inputSchemaZod }, (args) => catalogTools.importCatalog.execute(args));
26
29
  register(catalogTools.reviewImport.name, { description: catalogTools.reviewImport.description, inputSchema: catalogTools.reviewImport.inputSchemaZod }, (args) => catalogTools.reviewImport.execute(args));
27
30
  register(catalogTools.commitImport.name, { description: catalogTools.commitImport.description, inputSchema: catalogTools.commitImport.inputSchemaZod }, (args) => catalogTools.commitImport.execute(args));
@@ -74,5 +77,22 @@ export function createEstimatorServer() {
74
77
  register(clientTools.getClient.name, { description: clientTools.getClient.description, inputSchema: clientTools.getClient.inputSchemaZod }, (args) => clientTools.getClient.execute(args));
75
78
  register(clientTools.updateClient.name, { description: clientTools.updateClient.description, inputSchema: clientTools.updateClient.inputSchemaZod }, (args) => clientTools.updateClient.execute(args));
76
79
  register(clientTools.deleteClient.name, { description: clientTools.deleteClient.description, inputSchema: clientTools.deleteClient.inputSchemaZod }, (args) => clientTools.deleteClient.execute(args));
80
+ // --- Takeoff / approval tools (16) ---
81
+ register(takeoffTools.createTakeoffProject.name, { description: takeoffTools.createTakeoffProject.description, inputSchema: takeoffTools.createTakeoffProject.inputSchemaZod }, (args) => takeoffTools.createTakeoffProject.execute(args));
82
+ register(takeoffTools.getTakeoffProject.name, { description: takeoffTools.getTakeoffProject.description, inputSchema: takeoffTools.getTakeoffProject.inputSchemaZod }, (args) => takeoffTools.getTakeoffProject.execute(args));
83
+ register(takeoffTools.listTakeoffProjects.name, { description: takeoffTools.listTakeoffProjects.description, inputSchema: takeoffTools.listTakeoffProjects.inputSchemaZod }, (args) => takeoffTools.listTakeoffProjects.execute(args));
84
+ register(takeoffTools.uploadTakeoffInput.name, { description: takeoffTools.uploadTakeoffInput.description, inputSchema: takeoffTools.uploadTakeoffInput.inputSchemaZod }, (args) => takeoffTools.uploadTakeoffInput.execute(args));
85
+ register(takeoffTools.runTakeoffExtract.name, { description: takeoffTools.runTakeoffExtract.description, inputSchema: takeoffTools.runTakeoffExtract.inputSchemaZod }, (args) => takeoffTools.runTakeoffExtract.execute(args));
86
+ register(takeoffTools.listTakeoffRows.name, { description: takeoffTools.listTakeoffRows.description, inputSchema: takeoffTools.listTakeoffRows.inputSchemaZod }, (args) => takeoffTools.listTakeoffRows.execute(args));
87
+ register(takeoffTools.reviewTakeoffRow.name, { description: takeoffTools.reviewTakeoffRow.description, inputSchema: takeoffTools.reviewTakeoffRow.inputSchemaZod }, (args) => takeoffTools.reviewTakeoffRow.execute(args));
88
+ register(takeoffTools.commitTakeoff.name, { description: takeoffTools.commitTakeoff.description, inputSchema: takeoffTools.commitTakeoff.inputSchemaZod }, (args) => takeoffTools.commitTakeoff.execute(args));
89
+ register(takeoffTools.listSystemPacks.name, { description: takeoffTools.listSystemPacks.description, inputSchema: takeoffTools.listSystemPacks.inputSchemaZod }, (args) => takeoffTools.listSystemPacks.execute(args));
90
+ register(takeoffTools.applySystemPack.name, { description: takeoffTools.applySystemPack.description, inputSchema: takeoffTools.applySystemPack.inputSchemaZod }, (args) => takeoffTools.applySystemPack.execute(args));
91
+ register(takeoffTools.listServiceProfiles.name, { description: takeoffTools.listServiceProfiles.description, inputSchema: takeoffTools.listServiceProfiles.inputSchemaZod }, (args) => takeoffTools.listServiceProfiles.execute(args));
92
+ register(takeoffTools.upsertServiceProfile.name, { description: takeoffTools.upsertServiceProfile.description, inputSchema: takeoffTools.upsertServiceProfile.inputSchemaZod }, (args) => takeoffTools.upsertServiceProfile.execute(args));
93
+ register(takeoffTools.generateEstimateFromTakeoff.name, { description: takeoffTools.generateEstimateFromTakeoff.description, inputSchema: takeoffTools.generateEstimateFromTakeoff.inputSchemaZod }, (args) => takeoffTools.generateEstimateFromTakeoff.execute(args));
94
+ register(takeoffTools.requestEstimateApproval.name, { description: takeoffTools.requestEstimateApproval.description, inputSchema: takeoffTools.requestEstimateApproval.inputSchemaZod }, (args) => takeoffTools.requestEstimateApproval.execute(args));
95
+ register(takeoffTools.approveEstimate.name, { description: takeoffTools.approveEstimate.description, inputSchema: takeoffTools.approveEstimate.inputSchemaZod }, (args) => takeoffTools.approveEstimate.execute(args));
96
+ register(takeoffTools.getEstimateApproval.name, { description: takeoffTools.getEstimateApproval.description, inputSchema: takeoffTools.getEstimateApproval.inputSchemaZod }, (args) => takeoffTools.getEstimateApproval.execute(args));
77
97
  return server;
78
98
  }
@@ -60,15 +60,15 @@ declare const listClientsSchema: z.ZodObject<{
60
60
  limit: z.ZodOptional<z.ZodNumber>;
61
61
  cursor: z.ZodOptional<z.ZodString>;
62
62
  }, "strip", z.ZodTypeAny, {
63
+ search?: string | undefined;
63
64
  limit?: number | undefined;
64
65
  cursor?: string | undefined;
65
66
  companyId?: string | undefined;
66
- search?: string | undefined;
67
67
  }, {
68
+ search?: string | undefined;
68
69
  limit?: number | undefined;
69
70
  cursor?: string | undefined;
70
71
  companyId?: string | undefined;
71
- search?: string | undefined;
72
72
  }>;
73
73
  export declare const listClients: {
74
74
  name: string;
@@ -79,15 +79,15 @@ export declare const listClients: {
79
79
  limit: z.ZodOptional<z.ZodNumber>;
80
80
  cursor: z.ZodOptional<z.ZodString>;
81
81
  }, "strip", z.ZodTypeAny, {
82
+ search?: string | undefined;
82
83
  limit?: number | undefined;
83
84
  cursor?: string | undefined;
84
85
  companyId?: string | undefined;
85
- search?: string | undefined;
86
86
  }, {
87
+ search?: string | undefined;
87
88
  limit?: number | undefined;
88
89
  cursor?: string | undefined;
89
90
  companyId?: string | undefined;
90
- search?: string | undefined;
91
91
  }>;
92
92
  execute: (args: z.infer<typeof listClientsSchema>) => Promise<string>;
93
93
  };
@@ -47,13 +47,13 @@ declare const listCompaniesSchema: z.ZodObject<{
47
47
  limit: z.ZodOptional<z.ZodNumber>;
48
48
  cursor: z.ZodOptional<z.ZodString>;
49
49
  }, "strip", z.ZodTypeAny, {
50
+ search?: string | undefined;
50
51
  limit?: number | undefined;
51
52
  cursor?: string | undefined;
52
- search?: string | undefined;
53
53
  }, {
54
+ search?: string | undefined;
54
55
  limit?: number | undefined;
55
56
  cursor?: string | undefined;
56
- search?: string | undefined;
57
57
  }>;
58
58
  export declare const listCompanies: {
59
59
  name: string;
@@ -63,13 +63,13 @@ export declare const listCompanies: {
63
63
  limit: z.ZodOptional<z.ZodNumber>;
64
64
  cursor: z.ZodOptional<z.ZodString>;
65
65
  }, "strip", z.ZodTypeAny, {
66
+ search?: string | undefined;
66
67
  limit?: number | undefined;
67
68
  cursor?: string | undefined;
68
- search?: string | undefined;
69
69
  }, {
70
+ search?: string | undefined;
70
71
  limit?: number | undefined;
71
72
  cursor?: string | undefined;
72
- search?: string | undefined;
73
73
  }>;
74
74
  execute: (args: z.infer<typeof listCompaniesSchema>) => Promise<string>;
75
75
  };
@@ -0,0 +1,456 @@
1
+ import { z } from "zod";
2
+ declare const projectIdSchema: z.ZodObject<{
3
+ projectId: z.ZodString;
4
+ }, "strip", z.ZodTypeAny, {
5
+ projectId: string;
6
+ }, {
7
+ projectId: string;
8
+ }>;
9
+ declare const createTakeoffProjectSchema: z.ZodObject<{
10
+ projectName: z.ZodString;
11
+ buildingType: z.ZodEnum<["commercial", "residential"]>;
12
+ siteAddress: z.ZodOptional<z.ZodString>;
13
+ clientId: z.ZodOptional<z.ZodString>;
14
+ companyId: z.ZodOptional<z.ZodString>;
15
+ }, "strip", z.ZodTypeAny, {
16
+ projectName: string;
17
+ buildingType: "commercial" | "residential";
18
+ clientId?: string | undefined;
19
+ companyId?: string | undefined;
20
+ siteAddress?: string | undefined;
21
+ }, {
22
+ projectName: string;
23
+ buildingType: "commercial" | "residential";
24
+ clientId?: string | undefined;
25
+ companyId?: string | undefined;
26
+ siteAddress?: string | undefined;
27
+ }>;
28
+ declare const listTakeoffProjectsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
29
+ declare const uploadTakeoffInputSchema: z.ZodObject<{
30
+ projectId: z.ZodString;
31
+ kind: z.ZodString;
32
+ fileName: z.ZodString;
33
+ base64: z.ZodString;
34
+ mimeType: z.ZodOptional<z.ZodString>;
35
+ }, "strip", z.ZodTypeAny, {
36
+ base64: string;
37
+ projectId: string;
38
+ kind: string;
39
+ fileName: string;
40
+ mimeType?: string | undefined;
41
+ }, {
42
+ base64: string;
43
+ projectId: string;
44
+ kind: string;
45
+ fileName: string;
46
+ mimeType?: string | undefined;
47
+ }>;
48
+ declare const listTakeoffRowsSchema: z.ZodObject<{
49
+ projectId: z.ZodString;
50
+ reviewState: z.ZodOptional<z.ZodString>;
51
+ }, "strip", z.ZodTypeAny, {
52
+ projectId: string;
53
+ reviewState?: string | undefined;
54
+ }, {
55
+ projectId: string;
56
+ reviewState?: string | undefined;
57
+ }>;
58
+ declare const reviewTakeoffRowSchema: z.ZodObject<{
59
+ projectId: z.ZodString;
60
+ rowId: z.ZodString;
61
+ action: z.ZodEnum<["approve", "skip", "edit"]>;
62
+ edits: z.ZodOptional<z.ZodObject<{
63
+ description: z.ZodOptional<z.ZodString>;
64
+ quantity: z.ZodOptional<z.ZodNumber>;
65
+ unit: z.ZodOptional<z.ZodString>;
66
+ serviceCode: z.ZodOptional<z.ZodNullable<z.ZodString>>;
67
+ }, "strip", z.ZodTypeAny, {
68
+ serviceCode?: string | null | undefined;
69
+ unit?: string | undefined;
70
+ quantity?: number | undefined;
71
+ description?: string | undefined;
72
+ }, {
73
+ serviceCode?: string | null | undefined;
74
+ unit?: string | undefined;
75
+ quantity?: number | undefined;
76
+ description?: string | undefined;
77
+ }>>;
78
+ }, "strip", z.ZodTypeAny, {
79
+ action: "skip" | "edit" | "approve";
80
+ projectId: string;
81
+ rowId: string;
82
+ edits?: {
83
+ serviceCode?: string | null | undefined;
84
+ unit?: string | undefined;
85
+ quantity?: number | undefined;
86
+ description?: string | undefined;
87
+ } | undefined;
88
+ }, {
89
+ action: "skip" | "edit" | "approve";
90
+ projectId: string;
91
+ rowId: string;
92
+ edits?: {
93
+ serviceCode?: string | null | undefined;
94
+ unit?: string | undefined;
95
+ quantity?: number | undefined;
96
+ description?: string | undefined;
97
+ } | undefined;
98
+ }>;
99
+ declare const commitTakeoffSchema: z.ZodObject<{
100
+ projectId: z.ZodString;
101
+ overrideNote: z.ZodOptional<z.ZodString>;
102
+ }, "strip", z.ZodTypeAny, {
103
+ projectId: string;
104
+ overrideNote?: string | undefined;
105
+ }, {
106
+ projectId: string;
107
+ overrideNote?: string | undefined;
108
+ }>;
109
+ declare const listSystemPacksSchema: z.ZodObject<{
110
+ segment: z.ZodOptional<z.ZodEnum<["commercial", "residential"]>>;
111
+ }, "strip", z.ZodTypeAny, {
112
+ segment?: "commercial" | "residential" | undefined;
113
+ }, {
114
+ segment?: "commercial" | "residential" | undefined;
115
+ }>;
116
+ declare const applySystemPackSchema: z.ZodObject<{
117
+ projectId: z.ZodString;
118
+ systemPackSlug: z.ZodString;
119
+ serviceProfileId: z.ZodOptional<z.ZodString>;
120
+ }, "strip", z.ZodTypeAny, {
121
+ projectId: string;
122
+ systemPackSlug: string;
123
+ serviceProfileId?: string | undefined;
124
+ }, {
125
+ projectId: string;
126
+ systemPackSlug: string;
127
+ serviceProfileId?: string | undefined;
128
+ }>;
129
+ declare const listServiceProfilesSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
130
+ declare const upsertServiceProfileSchema: z.ZodObject<{
131
+ slug: z.ZodString;
132
+ name: z.ZodString;
133
+ segment: z.ZodEnum<["commercial_service", "commercial_production", "residential_service", "residential_production"]>;
134
+ laborMultiplier: z.ZodOptional<z.ZodNumber>;
135
+ defaultWastePercent: z.ZodOptional<z.ZodNumber>;
136
+ }, "strip", z.ZodTypeAny, {
137
+ name: string;
138
+ segment: "commercial_service" | "commercial_production" | "residential_service" | "residential_production";
139
+ slug: string;
140
+ laborMultiplier?: number | undefined;
141
+ defaultWastePercent?: number | undefined;
142
+ }, {
143
+ name: string;
144
+ segment: "commercial_service" | "commercial_production" | "residential_service" | "residential_production";
145
+ slug: string;
146
+ laborMultiplier?: number | undefined;
147
+ defaultWastePercent?: number | undefined;
148
+ }>;
149
+ declare const requestEstimateApprovalSchema: z.ZodObject<{
150
+ estimateId: z.ZodString;
151
+ note: z.ZodOptional<z.ZodString>;
152
+ }, "strip", z.ZodTypeAny, {
153
+ estimateId: string;
154
+ note?: string | undefined;
155
+ }, {
156
+ estimateId: string;
157
+ note?: string | undefined;
158
+ }>;
159
+ declare const approveEstimateSchema: z.ZodObject<{
160
+ estimateId: z.ZodString;
161
+ approvalId: z.ZodString;
162
+ action: z.ZodEnum<["approve", "reject"]>;
163
+ note: z.ZodOptional<z.ZodString>;
164
+ }, "strip", z.ZodTypeAny, {
165
+ action: "approve" | "reject";
166
+ estimateId: string;
167
+ approvalId: string;
168
+ note?: string | undefined;
169
+ }, {
170
+ action: "approve" | "reject";
171
+ estimateId: string;
172
+ approvalId: string;
173
+ note?: string | undefined;
174
+ }>;
175
+ declare const getEstimateApprovalSchema: z.ZodObject<{
176
+ estimateId: z.ZodString;
177
+ }, "strip", z.ZodTypeAny, {
178
+ estimateId: string;
179
+ }, {
180
+ estimateId: string;
181
+ }>;
182
+ export declare const createTakeoffProject: {
183
+ name: string;
184
+ description: string;
185
+ inputSchemaZod: z.ZodObject<{
186
+ projectName: z.ZodString;
187
+ buildingType: z.ZodEnum<["commercial", "residential"]>;
188
+ siteAddress: z.ZodOptional<z.ZodString>;
189
+ clientId: z.ZodOptional<z.ZodString>;
190
+ companyId: z.ZodOptional<z.ZodString>;
191
+ }, "strip", z.ZodTypeAny, {
192
+ projectName: string;
193
+ buildingType: "commercial" | "residential";
194
+ clientId?: string | undefined;
195
+ companyId?: string | undefined;
196
+ siteAddress?: string | undefined;
197
+ }, {
198
+ projectName: string;
199
+ buildingType: "commercial" | "residential";
200
+ clientId?: string | undefined;
201
+ companyId?: string | undefined;
202
+ siteAddress?: string | undefined;
203
+ }>;
204
+ execute: (args: z.infer<typeof createTakeoffProjectSchema>) => Promise<string>;
205
+ };
206
+ export declare const getTakeoffProject: {
207
+ name: string;
208
+ description: string;
209
+ inputSchemaZod: z.ZodObject<{
210
+ projectId: z.ZodString;
211
+ }, "strip", z.ZodTypeAny, {
212
+ projectId: string;
213
+ }, {
214
+ projectId: string;
215
+ }>;
216
+ execute: (args: z.infer<typeof projectIdSchema>) => Promise<string>;
217
+ };
218
+ export declare const listTakeoffProjects: {
219
+ name: string;
220
+ description: string;
221
+ inputSchemaZod: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
222
+ execute: (_args: z.infer<typeof listTakeoffProjectsSchema>) => Promise<string>;
223
+ };
224
+ export declare const uploadTakeoffInput: {
225
+ name: string;
226
+ description: string;
227
+ inputSchemaZod: z.ZodObject<{
228
+ projectId: z.ZodString;
229
+ kind: z.ZodString;
230
+ fileName: z.ZodString;
231
+ base64: z.ZodString;
232
+ mimeType: z.ZodOptional<z.ZodString>;
233
+ }, "strip", z.ZodTypeAny, {
234
+ base64: string;
235
+ projectId: string;
236
+ kind: string;
237
+ fileName: string;
238
+ mimeType?: string | undefined;
239
+ }, {
240
+ base64: string;
241
+ projectId: string;
242
+ kind: string;
243
+ fileName: string;
244
+ mimeType?: string | undefined;
245
+ }>;
246
+ execute: (args: z.infer<typeof uploadTakeoffInputSchema>) => Promise<string>;
247
+ };
248
+ export declare const runTakeoffExtract: {
249
+ name: string;
250
+ description: string;
251
+ inputSchemaZod: z.ZodObject<{
252
+ projectId: z.ZodString;
253
+ }, "strip", z.ZodTypeAny, {
254
+ projectId: string;
255
+ }, {
256
+ projectId: string;
257
+ }>;
258
+ execute: (args: z.infer<typeof projectIdSchema>) => Promise<string>;
259
+ };
260
+ export declare const listTakeoffRows: {
261
+ name: string;
262
+ description: string;
263
+ inputSchemaZod: z.ZodObject<{
264
+ projectId: z.ZodString;
265
+ reviewState: z.ZodOptional<z.ZodString>;
266
+ }, "strip", z.ZodTypeAny, {
267
+ projectId: string;
268
+ reviewState?: string | undefined;
269
+ }, {
270
+ projectId: string;
271
+ reviewState?: string | undefined;
272
+ }>;
273
+ execute: (args: z.infer<typeof listTakeoffRowsSchema>) => Promise<string>;
274
+ };
275
+ export declare const reviewTakeoffRow: {
276
+ name: string;
277
+ description: string;
278
+ inputSchemaZod: z.ZodObject<{
279
+ projectId: z.ZodString;
280
+ rowId: z.ZodString;
281
+ action: z.ZodEnum<["approve", "skip", "edit"]>;
282
+ edits: z.ZodOptional<z.ZodObject<{
283
+ description: z.ZodOptional<z.ZodString>;
284
+ quantity: z.ZodOptional<z.ZodNumber>;
285
+ unit: z.ZodOptional<z.ZodString>;
286
+ serviceCode: z.ZodOptional<z.ZodNullable<z.ZodString>>;
287
+ }, "strip", z.ZodTypeAny, {
288
+ serviceCode?: string | null | undefined;
289
+ unit?: string | undefined;
290
+ quantity?: number | undefined;
291
+ description?: string | undefined;
292
+ }, {
293
+ serviceCode?: string | null | undefined;
294
+ unit?: string | undefined;
295
+ quantity?: number | undefined;
296
+ description?: string | undefined;
297
+ }>>;
298
+ }, "strip", z.ZodTypeAny, {
299
+ action: "skip" | "edit" | "approve";
300
+ projectId: string;
301
+ rowId: string;
302
+ edits?: {
303
+ serviceCode?: string | null | undefined;
304
+ unit?: string | undefined;
305
+ quantity?: number | undefined;
306
+ description?: string | undefined;
307
+ } | undefined;
308
+ }, {
309
+ action: "skip" | "edit" | "approve";
310
+ projectId: string;
311
+ rowId: string;
312
+ edits?: {
313
+ serviceCode?: string | null | undefined;
314
+ unit?: string | undefined;
315
+ quantity?: number | undefined;
316
+ description?: string | undefined;
317
+ } | undefined;
318
+ }>;
319
+ execute: (args: z.infer<typeof reviewTakeoffRowSchema>) => Promise<string>;
320
+ };
321
+ export declare const commitTakeoff: {
322
+ name: string;
323
+ description: string;
324
+ inputSchemaZod: z.ZodObject<{
325
+ projectId: z.ZodString;
326
+ overrideNote: z.ZodOptional<z.ZodString>;
327
+ }, "strip", z.ZodTypeAny, {
328
+ projectId: string;
329
+ overrideNote?: string | undefined;
330
+ }, {
331
+ projectId: string;
332
+ overrideNote?: string | undefined;
333
+ }>;
334
+ execute: (args: z.infer<typeof commitTakeoffSchema>) => Promise<string>;
335
+ };
336
+ export declare const listSystemPacks: {
337
+ name: string;
338
+ description: string;
339
+ inputSchemaZod: z.ZodObject<{
340
+ segment: z.ZodOptional<z.ZodEnum<["commercial", "residential"]>>;
341
+ }, "strip", z.ZodTypeAny, {
342
+ segment?: "commercial" | "residential" | undefined;
343
+ }, {
344
+ segment?: "commercial" | "residential" | undefined;
345
+ }>;
346
+ execute: (args: z.infer<typeof listSystemPacksSchema>) => Promise<string>;
347
+ };
348
+ export declare const applySystemPack: {
349
+ name: string;
350
+ description: string;
351
+ inputSchemaZod: z.ZodObject<{
352
+ projectId: z.ZodString;
353
+ systemPackSlug: z.ZodString;
354
+ serviceProfileId: z.ZodOptional<z.ZodString>;
355
+ }, "strip", z.ZodTypeAny, {
356
+ projectId: string;
357
+ systemPackSlug: string;
358
+ serviceProfileId?: string | undefined;
359
+ }, {
360
+ projectId: string;
361
+ systemPackSlug: string;
362
+ serviceProfileId?: string | undefined;
363
+ }>;
364
+ execute: (args: z.infer<typeof applySystemPackSchema>) => Promise<string>;
365
+ };
366
+ export declare const listServiceProfiles: {
367
+ name: string;
368
+ description: string;
369
+ inputSchemaZod: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
370
+ execute: (_args: z.infer<typeof listServiceProfilesSchema>) => Promise<string>;
371
+ };
372
+ export declare const upsertServiceProfile: {
373
+ name: string;
374
+ description: string;
375
+ inputSchemaZod: z.ZodObject<{
376
+ slug: z.ZodString;
377
+ name: z.ZodString;
378
+ segment: z.ZodEnum<["commercial_service", "commercial_production", "residential_service", "residential_production"]>;
379
+ laborMultiplier: z.ZodOptional<z.ZodNumber>;
380
+ defaultWastePercent: z.ZodOptional<z.ZodNumber>;
381
+ }, "strip", z.ZodTypeAny, {
382
+ name: string;
383
+ segment: "commercial_service" | "commercial_production" | "residential_service" | "residential_production";
384
+ slug: string;
385
+ laborMultiplier?: number | undefined;
386
+ defaultWastePercent?: number | undefined;
387
+ }, {
388
+ name: string;
389
+ segment: "commercial_service" | "commercial_production" | "residential_service" | "residential_production";
390
+ slug: string;
391
+ laborMultiplier?: number | undefined;
392
+ defaultWastePercent?: number | undefined;
393
+ }>;
394
+ execute: (args: z.infer<typeof upsertServiceProfileSchema>) => Promise<string>;
395
+ };
396
+ export declare const generateEstimateFromTakeoff: {
397
+ name: string;
398
+ description: string;
399
+ inputSchemaZod: z.ZodObject<{
400
+ projectId: z.ZodString;
401
+ }, "strip", z.ZodTypeAny, {
402
+ projectId: string;
403
+ }, {
404
+ projectId: string;
405
+ }>;
406
+ execute: (args: z.infer<typeof projectIdSchema>) => Promise<string>;
407
+ };
408
+ export declare const requestEstimateApproval: {
409
+ name: string;
410
+ description: string;
411
+ inputSchemaZod: z.ZodObject<{
412
+ estimateId: z.ZodString;
413
+ note: z.ZodOptional<z.ZodString>;
414
+ }, "strip", z.ZodTypeAny, {
415
+ estimateId: string;
416
+ note?: string | undefined;
417
+ }, {
418
+ estimateId: string;
419
+ note?: string | undefined;
420
+ }>;
421
+ execute: (args: z.infer<typeof requestEstimateApprovalSchema>) => Promise<string>;
422
+ };
423
+ export declare const approveEstimate: {
424
+ name: string;
425
+ description: string;
426
+ inputSchemaZod: z.ZodObject<{
427
+ estimateId: z.ZodString;
428
+ approvalId: z.ZodString;
429
+ action: z.ZodEnum<["approve", "reject"]>;
430
+ note: z.ZodOptional<z.ZodString>;
431
+ }, "strip", z.ZodTypeAny, {
432
+ action: "approve" | "reject";
433
+ estimateId: string;
434
+ approvalId: string;
435
+ note?: string | undefined;
436
+ }, {
437
+ action: "approve" | "reject";
438
+ estimateId: string;
439
+ approvalId: string;
440
+ note?: string | undefined;
441
+ }>;
442
+ execute: (args: z.infer<typeof approveEstimateSchema>) => Promise<string>;
443
+ };
444
+ export declare const getEstimateApproval: {
445
+ name: string;
446
+ description: string;
447
+ inputSchemaZod: z.ZodObject<{
448
+ estimateId: z.ZodString;
449
+ }, "strip", z.ZodTypeAny, {
450
+ estimateId: string;
451
+ }, {
452
+ estimateId: string;
453
+ }>;
454
+ execute: (args: z.infer<typeof getEstimateApprovalSchema>) => Promise<string>;
455
+ };
456
+ export {};
@@ -0,0 +1,263 @@
1
+ import { z } from "zod";
2
+ import * as client from "../lib/kynver-client.js";
3
+ const projectIdSchema = z.object({ projectId: z.string().uuid() });
4
+ const createTakeoffProjectSchema = z.object({
5
+ projectName: z.string(),
6
+ buildingType: z.enum(["commercial", "residential"]),
7
+ siteAddress: z.string().optional(),
8
+ clientId: z.string().optional(),
9
+ companyId: z.string().optional(),
10
+ });
11
+ const listTakeoffProjectsSchema = z.object({});
12
+ const uploadTakeoffInputSchema = z.object({
13
+ projectId: z.string().uuid(),
14
+ kind: z.string(),
15
+ fileName: z.string(),
16
+ base64: z.string(),
17
+ mimeType: z.string().optional(),
18
+ });
19
+ const listTakeoffRowsSchema = z.object({
20
+ projectId: z.string().uuid(),
21
+ reviewState: z.string().optional(),
22
+ });
23
+ const reviewTakeoffRowSchema = z.object({
24
+ projectId: z.string().uuid(),
25
+ rowId: z.string().uuid(),
26
+ action: z.enum(["approve", "skip", "edit"]),
27
+ edits: z
28
+ .object({
29
+ description: z.string().optional(),
30
+ quantity: z.number().optional(),
31
+ unit: z.string().optional(),
32
+ serviceCode: z.string().nullable().optional(),
33
+ })
34
+ .optional(),
35
+ });
36
+ const commitTakeoffSchema = z.object({
37
+ projectId: z.string().uuid(),
38
+ overrideNote: z.string().optional(),
39
+ });
40
+ const listSystemPacksSchema = z.object({ segment: z.enum(["commercial", "residential"]).optional() });
41
+ const applySystemPackSchema = z.object({
42
+ projectId: z.string().uuid(),
43
+ systemPackSlug: z.string(),
44
+ serviceProfileId: z.string().optional(),
45
+ });
46
+ const listServiceProfilesSchema = z.object({});
47
+ const upsertServiceProfileSchema = z.object({
48
+ slug: z.string(),
49
+ name: z.string(),
50
+ segment: z.enum([
51
+ "commercial_service",
52
+ "commercial_production",
53
+ "residential_service",
54
+ "residential_production",
55
+ ]),
56
+ laborMultiplier: z.number().optional(),
57
+ defaultWastePercent: z.number().optional(),
58
+ });
59
+ const requestEstimateApprovalSchema = z.object({
60
+ estimateId: z.string().uuid(),
61
+ note: z.string().optional(),
62
+ });
63
+ const approveEstimateSchema = z.object({
64
+ estimateId: z.string().uuid(),
65
+ approvalId: z.string().uuid(),
66
+ action: z.enum(["approve", "reject"]),
67
+ note: z.string().optional(),
68
+ });
69
+ const getEstimateApprovalSchema = z.object({ estimateId: z.string().uuid() });
70
+ export const createTakeoffProject = {
71
+ name: "estimator_create_takeoff_project",
72
+ description: "Create a takeoff project shell (commercial or residential). Upload inputs, run extract, review rows, commit, apply system pack, then generate estimate.",
73
+ inputSchemaZod: createTakeoffProjectSchema,
74
+ execute: async (args) => {
75
+ if (!client.hasApiKey())
76
+ return client.noApiKeyMessage();
77
+ const data = await client.post("/estimator/takeoffs", args);
78
+ return JSON.stringify(data);
79
+ },
80
+ };
81
+ export const getTakeoffProject = {
82
+ name: "estimator_get_takeoff_project",
83
+ description: "Get takeoff project with inputs, rows, status, pack/profile, linked estimate.",
84
+ inputSchemaZod: projectIdSchema,
85
+ execute: async (args) => {
86
+ if (!client.hasApiKey())
87
+ return client.noApiKeyMessage();
88
+ const data = await client.get(`/estimator/takeoffs/${args.projectId}`);
89
+ return JSON.stringify(data);
90
+ },
91
+ };
92
+ export const listTakeoffProjects = {
93
+ name: "estimator_list_takeoff_projects",
94
+ description: "List takeoff projects for the workspace.",
95
+ inputSchemaZod: listTakeoffProjectsSchema,
96
+ execute: async (_args) => {
97
+ if (!client.hasApiKey())
98
+ return client.noApiKeyMessage();
99
+ const data = await client.get("/estimator/takeoffs");
100
+ return JSON.stringify(data);
101
+ },
102
+ };
103
+ export const uploadTakeoffInput = {
104
+ name: "estimator_upload_takeoff_input",
105
+ description: "Register a takeoff file upload. Pass base64 file content via the API route from your client, or use the web UI multipart upload at POST /api/estimator/takeoffs/{id}/inputs with kind + file.",
106
+ inputSchemaZod: uploadTakeoffInputSchema,
107
+ execute: async (args) => {
108
+ if (!client.hasApiKey())
109
+ return client.noApiKeyMessage();
110
+ return JSON.stringify({
111
+ message: "Use POST /api/estimator/takeoffs/{projectId}/inputs multipart (kind + file) from an authenticated client. MCP base64 upload route can be added when hosted file relay is available.",
112
+ projectId: args.projectId,
113
+ kind: args.kind,
114
+ fileName: args.fileName,
115
+ });
116
+ },
117
+ };
118
+ export const runTakeoffExtract = {
119
+ name: "estimator_run_takeoff_extract",
120
+ description: "Run takeoff extractors on all uploaded inputs (idempotent).",
121
+ inputSchemaZod: projectIdSchema,
122
+ execute: async (args) => {
123
+ if (!client.hasApiKey())
124
+ return client.noApiKeyMessage();
125
+ const data = await client.post(`/estimator/takeoffs/${args.projectId}/extract`, {});
126
+ return JSON.stringify(data);
127
+ },
128
+ };
129
+ export const listTakeoffRows = {
130
+ name: "estimator_list_takeoff_rows",
131
+ description: "List takeoff rows with evidence and review state.",
132
+ inputSchemaZod: listTakeoffRowsSchema,
133
+ execute: async (args) => {
134
+ if (!client.hasApiKey())
135
+ return client.noApiKeyMessage();
136
+ const qs = args.reviewState ? `?reviewState=${args.reviewState}` : "";
137
+ const data = await client.get(`/estimator/takeoffs/${args.projectId}/rows${qs}`);
138
+ return JSON.stringify(data);
139
+ },
140
+ };
141
+ export const reviewTakeoffRow = {
142
+ name: "estimator_review_takeoff_row",
143
+ description: "Approve, skip, or edit a single takeoff row.",
144
+ inputSchemaZod: reviewTakeoffRowSchema,
145
+ execute: async (args) => {
146
+ if (!client.hasApiKey())
147
+ return client.noApiKeyMessage();
148
+ const { projectId, rowId, ...body } = args;
149
+ const data = await client.patch(`/estimator/takeoffs/${projectId}/rows/${rowId}`, body);
150
+ return JSON.stringify(data);
151
+ },
152
+ };
153
+ export const commitTakeoff = {
154
+ name: "estimator_commit_takeoff",
155
+ description: "Mark takeoff ready after all rows reviewed and area measurement approved (or override note).",
156
+ inputSchemaZod: commitTakeoffSchema,
157
+ execute: async (args) => {
158
+ if (!client.hasApiKey())
159
+ return client.noApiKeyMessage();
160
+ const data = await client.post(`/estimator/takeoffs/${args.projectId}/commit`, {
161
+ overrideNote: args.overrideNote,
162
+ });
163
+ return JSON.stringify(data);
164
+ },
165
+ };
166
+ export const listSystemPacks = {
167
+ name: "estimator_list_system_packs",
168
+ description: "List built-in commercial and residential system packs.",
169
+ inputSchemaZod: listSystemPacksSchema,
170
+ execute: async (args) => {
171
+ if (!client.hasApiKey())
172
+ return client.noApiKeyMessage();
173
+ const qs = args.segment ? `?segment=${args.segment}` : "";
174
+ const data = await client.get(`/estimator/system-packs${qs}`);
175
+ return JSON.stringify(data);
176
+ },
177
+ };
178
+ export const applySystemPack = {
179
+ name: "estimator_apply_system_pack",
180
+ description: "Set system pack slug and optional service profile on takeoff project.",
181
+ inputSchemaZod: applySystemPackSchema,
182
+ execute: async (args) => {
183
+ if (!client.hasApiKey())
184
+ return client.noApiKeyMessage();
185
+ const data = await client.patch(`/estimator/takeoffs/${args.projectId}`, {
186
+ systemPackSlug: args.systemPackSlug,
187
+ serviceProfileId: args.serviceProfileId ?? null,
188
+ });
189
+ return JSON.stringify(data);
190
+ },
191
+ };
192
+ export const listServiceProfiles = {
193
+ name: "estimator_list_service_profiles",
194
+ description: "List service department profiles (includes v6 defaults).",
195
+ inputSchemaZod: listServiceProfilesSchema,
196
+ execute: async (_args) => {
197
+ if (!client.hasApiKey())
198
+ return client.noApiKeyMessage();
199
+ const data = await client.get("/estimator/service-profiles");
200
+ return JSON.stringify(data);
201
+ },
202
+ };
203
+ export const upsertServiceProfile = {
204
+ name: "estimator_upsert_service_profile",
205
+ description: "Create or update a service department profile.",
206
+ inputSchemaZod: upsertServiceProfileSchema,
207
+ execute: async (args) => {
208
+ if (!client.hasApiKey())
209
+ return client.noApiKeyMessage();
210
+ const data = await client.post("/estimator/service-profiles", args);
211
+ return JSON.stringify(data);
212
+ },
213
+ };
214
+ export const generateEstimateFromTakeoff = {
215
+ name: "estimator_generate_estimate_from_takeoff",
216
+ description: "Generate draft estimate from committed takeoff + system pack. Preflight fails if catalog codes missing.",
217
+ inputSchemaZod: projectIdSchema,
218
+ execute: async (args) => {
219
+ if (!client.hasApiKey())
220
+ return client.noApiKeyMessage();
221
+ const data = await client.post(`/estimator/takeoffs/${args.projectId}/generate-estimate`, {});
222
+ return JSON.stringify(data);
223
+ },
224
+ };
225
+ export const requestEstimateApproval = {
226
+ name: "estimator_request_estimate_approval",
227
+ description: "Request owner approval before customer send.",
228
+ inputSchemaZod: requestEstimateApprovalSchema,
229
+ execute: async (args) => {
230
+ if (!client.hasApiKey())
231
+ return client.noApiKeyMessage();
232
+ const data = await client.post(`/estimator/estimates/${args.estimateId}/approval`, {
233
+ note: args.note,
234
+ });
235
+ return JSON.stringify(data);
236
+ },
237
+ };
238
+ export const approveEstimate = {
239
+ name: "estimator_approve_estimate",
240
+ description: "Approve or reject estimate (workspace owner).",
241
+ inputSchemaZod: approveEstimateSchema,
242
+ execute: async (args) => {
243
+ if (!client.hasApiKey())
244
+ return client.noApiKeyMessage();
245
+ const data = await client.post(`/estimator/estimates/${args.estimateId}/approval`, {
246
+ approvalId: args.approvalId,
247
+ action: args.action,
248
+ note: args.note,
249
+ });
250
+ return JSON.stringify(data);
251
+ },
252
+ };
253
+ export const getEstimateApproval = {
254
+ name: "estimator_get_estimate_approval",
255
+ description: "Get latest approval status for an estimate.",
256
+ inputSchemaZod: getEstimateApprovalSchema,
257
+ execute: async (args) => {
258
+ if (!client.hasApiKey())
259
+ return client.noApiKeyMessage();
260
+ const data = await client.get(`/estimator/estimates/${args.estimateId}/approval`);
261
+ return JSON.stringify(data);
262
+ },
263
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kynver-app/mcp-estimator",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Kynver Estimator MCP server — chat, estimate, and catalog tools",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -1,13 +1,18 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Kynver Estimator MCP server — stdio entry point.
4
- * Run: node dist/index.js
5
- * Env: KYNVER_API_URL (default https://kynver.com), KYNVER_API_KEY (required for all tools)
6
- */
7
-
8
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
- import { createEstimatorServer } from "./server.js";
10
-
11
- const server = createEstimatorServer();
12
- const transport = new StdioServerTransport();
13
- await server.connect(transport);
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Kynver Estimator MCP server — stdio entry point.
4
+ * Run: node dist/index.js
5
+ * Env: KYNVER_API_URL (default https://kynver.com), KYNVER_API_KEY (required for all tools)
6
+ */
7
+
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { handleCliVersionFlag } from "./lib/package-version.js";
10
+ import { createEstimatorServer } from "./server.js";
11
+
12
+ if (handleCliVersionFlag(process.argv.slice(2), import.meta.url, "kynver-mcp-estimator")) {
13
+ // exits
14
+ }
15
+
16
+ const server = createEstimatorServer();
17
+ const transport = new StdioServerTransport();
18
+ await server.connect(transport);
@@ -1,6 +1,6 @@
1
- // @generated — synced from packages/kynver-mcp-sse-lib/src/index.ts via scripts/sync-mcp-sse-server.mjs
2
- // Do not edit by hand; change the canonical file and re-run the sync script.
3
-
1
+ // @generated — synced from packages/kynver-mcp-sse-lib/src/index.ts via scripts/sync-mcp-sse-server.mjs
2
+ // Do not edit by hand; change the canonical file and re-run the sync script.
3
+
4
4
  import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
5
5
  import { timingSafeEqual } from "node:crypto";
6
6
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
@@ -0,0 +1,55 @@
1
+ // @generated — synced from packages/kynver-mcp-sse-lib/src/package-version.ts via scripts/sync-mcp-sse-server.mjs
2
+ // Do not edit by hand; change the canonical file and re-run the sync script.
3
+
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ function resolvePackageRoot(moduleUrl: string): string {
9
+ let dir = dirname(fileURLToPath(moduleUrl));
10
+ for (let depth = 0; depth < 6; depth += 1) {
11
+ if (existsSync(join(dir, "package.json"))) return dir;
12
+ const parent = dirname(dir);
13
+ if (parent === dir) break;
14
+ dir = parent;
15
+ }
16
+ throw new Error(`package.json not found above ${dirname(fileURLToPath(moduleUrl))}`);
17
+ }
18
+
19
+ /** Read a field from the nearest `package.json` above this module. */
20
+ export function readOwnPackageJsonField(
21
+ moduleUrl: string,
22
+ field: "version" | "name",
23
+ ): string {
24
+ const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
25
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as Record<string, unknown>;
26
+ const value = pkg[field];
27
+ if (typeof value !== "string" || !value.trim()) {
28
+ throw new Error(`Missing package.json ${field} at ${pkgPath}`);
29
+ }
30
+ return value;
31
+ }
32
+
33
+ export function readOwnPackageVersion(moduleUrl: string = import.meta.url): string {
34
+ return readOwnPackageJsonField(moduleUrl, "version");
35
+ }
36
+
37
+ export function wantsCliVersion(argv: string[]): boolean {
38
+ return argv.some((arg) => arg === "--version" || arg === "-v");
39
+ }
40
+
41
+ export function printCliVersionAndExit(version: string, binName?: string): never {
42
+ console.log(binName ? `${binName} ${version}` : version);
43
+ process.exit(0);
44
+ }
45
+
46
+ /** When `--version` / `-v` is present, print package version and exit before side effects. */
47
+ export function handleCliVersionFlag(
48
+ argv: string[],
49
+ moduleUrl: string = import.meta.url,
50
+ binName?: string,
51
+ ): boolean {
52
+ if (!wantsCliVersion(argv)) return false;
53
+ printCliVersionAndExit(readOwnPackageVersion(moduleUrl), binName);
54
+ return true;
55
+ }
package/src/server.ts CHANGED
@@ -14,6 +14,9 @@ import * as visionTools from "./tools/vision-tools.js";
14
14
  import * as companyTools from "./tools/company-tools.js";
15
15
  import * as clientTools from "./tools/client-tools.js";
16
16
  import * as takeoffTools from "./tools/takeoff-tools.js";
17
+ import { readOwnPackageVersion } from "./lib/package-version.js";
18
+
19
+ const PACKAGE_VERSION = readOwnPackageVersion(import.meta.url);
17
20
 
18
21
  type ToolConfig = { description: string; inputSchema: unknown };
19
22
 
@@ -23,7 +26,7 @@ function toolResult(text: string) {
23
26
 
24
27
  export function createEstimatorServer(): McpServer {
25
28
  const server = new McpServer(
26
- { name: "kynver-mcp-estimator", version: "0.2.0" },
29
+ { name: "kynver-mcp-estimator", version: PACKAGE_VERSION },
27
30
  { capabilities: { tools: {} } }
28
31
  );
29
32
 
@@ -3,18 +3,91 @@ import * as client from "../lib/kynver-client.js";
3
3
 
4
4
  const projectIdSchema = z.object({ projectId: z.string().uuid() });
5
5
 
6
+ const createTakeoffProjectSchema = z.object({
7
+ projectName: z.string(),
8
+ buildingType: z.enum(["commercial", "residential"]),
9
+ siteAddress: z.string().optional(),
10
+ clientId: z.string().optional(),
11
+ companyId: z.string().optional(),
12
+ });
13
+
14
+ const listTakeoffProjectsSchema = z.object({});
15
+
16
+ const uploadTakeoffInputSchema = z.object({
17
+ projectId: z.string().uuid(),
18
+ kind: z.string(),
19
+ fileName: z.string(),
20
+ base64: z.string(),
21
+ mimeType: z.string().optional(),
22
+ });
23
+
24
+ const listTakeoffRowsSchema = z.object({
25
+ projectId: z.string().uuid(),
26
+ reviewState: z.string().optional(),
27
+ });
28
+
29
+ const reviewTakeoffRowSchema = z.object({
30
+ projectId: z.string().uuid(),
31
+ rowId: z.string().uuid(),
32
+ action: z.enum(["approve", "skip", "edit"]),
33
+ edits: z
34
+ .object({
35
+ description: z.string().optional(),
36
+ quantity: z.number().optional(),
37
+ unit: z.string().optional(),
38
+ serviceCode: z.string().nullable().optional(),
39
+ })
40
+ .optional(),
41
+ });
42
+
43
+ const commitTakeoffSchema = z.object({
44
+ projectId: z.string().uuid(),
45
+ overrideNote: z.string().optional(),
46
+ });
47
+
48
+ const listSystemPacksSchema = z.object({ segment: z.enum(["commercial", "residential"]).optional() });
49
+
50
+ const applySystemPackSchema = z.object({
51
+ projectId: z.string().uuid(),
52
+ systemPackSlug: z.string(),
53
+ serviceProfileId: z.string().optional(),
54
+ });
55
+
56
+ const listServiceProfilesSchema = z.object({});
57
+
58
+ const upsertServiceProfileSchema = z.object({
59
+ slug: z.string(),
60
+ name: z.string(),
61
+ segment: z.enum([
62
+ "commercial_service",
63
+ "commercial_production",
64
+ "residential_service",
65
+ "residential_production",
66
+ ]),
67
+ laborMultiplier: z.number().optional(),
68
+ defaultWastePercent: z.number().optional(),
69
+ });
70
+
71
+ const requestEstimateApprovalSchema = z.object({
72
+ estimateId: z.string().uuid(),
73
+ note: z.string().optional(),
74
+ });
75
+
76
+ const approveEstimateSchema = z.object({
77
+ estimateId: z.string().uuid(),
78
+ approvalId: z.string().uuid(),
79
+ action: z.enum(["approve", "reject"]),
80
+ note: z.string().optional(),
81
+ });
82
+
83
+ const getEstimateApprovalSchema = z.object({ estimateId: z.string().uuid() });
84
+
6
85
  export const createTakeoffProject = {
7
86
  name: "estimator_create_takeoff_project",
8
87
  description:
9
88
  "Create a takeoff project shell (commercial or residential). Upload inputs, run extract, review rows, commit, apply system pack, then generate estimate.",
10
- inputSchemaZod: z.object({
11
- projectName: z.string(),
12
- buildingType: z.enum(["commercial", "residential"]),
13
- siteAddress: z.string().optional(),
14
- clientId: z.string().optional(),
15
- companyId: z.string().optional(),
16
- }),
17
- execute: async (args: z.infer<typeof createTakeoffProject.inputSchemaZod>): Promise<string> => {
89
+ inputSchemaZod: createTakeoffProjectSchema,
90
+ execute: async (args: z.infer<typeof createTakeoffProjectSchema>): Promise<string> => {
18
91
  if (!client.hasApiKey()) return client.noApiKeyMessage();
19
92
  const data = await client.post("/estimator/takeoffs", args);
20
93
  return JSON.stringify(data);
@@ -35,8 +108,8 @@ export const getTakeoffProject = {
35
108
  export const listTakeoffProjects = {
36
109
  name: "estimator_list_takeoff_projects",
37
110
  description: "List takeoff projects for the workspace.",
38
- inputSchemaZod: z.object({}),
39
- execute: async (): Promise<string> => {
111
+ inputSchemaZod: listTakeoffProjectsSchema,
112
+ execute: async (_args: z.infer<typeof listTakeoffProjectsSchema>): Promise<string> => {
40
113
  if (!client.hasApiKey()) return client.noApiKeyMessage();
41
114
  const data = await client.get("/estimator/takeoffs");
42
115
  return JSON.stringify(data);
@@ -47,14 +120,8 @@ export const uploadTakeoffInput = {
47
120
  name: "estimator_upload_takeoff_input",
48
121
  description:
49
122
  "Register a takeoff file upload. Pass base64 file content via the API route from your client, or use the web UI multipart upload at POST /api/estimator/takeoffs/{id}/inputs with kind + file.",
50
- inputSchemaZod: z.object({
51
- projectId: z.string().uuid(),
52
- kind: z.string(),
53
- fileName: z.string(),
54
- base64: z.string(),
55
- mimeType: z.string().optional(),
56
- }),
57
- execute: async (args: z.infer<typeof uploadTakeoffInput.inputSchemaZod>): Promise<string> => {
123
+ inputSchemaZod: uploadTakeoffInputSchema,
124
+ execute: async (args: z.infer<typeof uploadTakeoffInputSchema>): Promise<string> => {
58
125
  if (!client.hasApiKey()) return client.noApiKeyMessage();
59
126
  return JSON.stringify({
60
127
  message:
@@ -80,11 +147,8 @@ export const runTakeoffExtract = {
80
147
  export const listTakeoffRows = {
81
148
  name: "estimator_list_takeoff_rows",
82
149
  description: "List takeoff rows with evidence and review state.",
83
- inputSchemaZod: z.object({
84
- projectId: z.string().uuid(),
85
- reviewState: z.string().optional(),
86
- }),
87
- execute: async (args: z.infer<typeof listTakeoffRows.inputSchemaZod>): Promise<string> => {
150
+ inputSchemaZod: listTakeoffRowsSchema,
151
+ execute: async (args: z.infer<typeof listTakeoffRowsSchema>): Promise<string> => {
88
152
  if (!client.hasApiKey()) return client.noApiKeyMessage();
89
153
  const qs = args.reviewState ? `?reviewState=${args.reviewState}` : "";
90
154
  const data = await client.get(`/estimator/takeoffs/${args.projectId}/rows${qs}`);
@@ -95,20 +159,8 @@ export const listTakeoffRows = {
95
159
  export const reviewTakeoffRow = {
96
160
  name: "estimator_review_takeoff_row",
97
161
  description: "Approve, skip, or edit a single takeoff row.",
98
- inputSchemaZod: z.object({
99
- projectId: z.string().uuid(),
100
- rowId: z.string().uuid(),
101
- action: z.enum(["approve", "skip", "edit"]),
102
- edits: z
103
- .object({
104
- description: z.string().optional(),
105
- quantity: z.number().optional(),
106
- unit: z.string().optional(),
107
- serviceCode: z.string().nullable().optional(),
108
- })
109
- .optional(),
110
- }),
111
- execute: async (args: z.infer<typeof reviewTakeoffRow.inputSchemaZod>): Promise<string> => {
162
+ inputSchemaZod: reviewTakeoffRowSchema,
163
+ execute: async (args: z.infer<typeof reviewTakeoffRowSchema>): Promise<string> => {
112
164
  if (!client.hasApiKey()) return client.noApiKeyMessage();
113
165
  const { projectId, rowId, ...body } = args;
114
166
  const data = await client.patch(`/estimator/takeoffs/${projectId}/rows/${rowId}`, body);
@@ -119,11 +171,8 @@ export const reviewTakeoffRow = {
119
171
  export const commitTakeoff = {
120
172
  name: "estimator_commit_takeoff",
121
173
  description: "Mark takeoff ready after all rows reviewed and area measurement approved (or override note).",
122
- inputSchemaZod: z.object({
123
- projectId: z.string().uuid(),
124
- overrideNote: z.string().optional(),
125
- }),
126
- execute: async (args: z.infer<typeof commitTakeoff.inputSchemaZod>): Promise<string> => {
174
+ inputSchemaZod: commitTakeoffSchema,
175
+ execute: async (args: z.infer<typeof commitTakeoffSchema>): Promise<string> => {
127
176
  if (!client.hasApiKey()) return client.noApiKeyMessage();
128
177
  const data = await client.post(`/estimator/takeoffs/${args.projectId}/commit`, {
129
178
  overrideNote: args.overrideNote,
@@ -135,8 +184,8 @@ export const commitTakeoff = {
135
184
  export const listSystemPacks = {
136
185
  name: "estimator_list_system_packs",
137
186
  description: "List built-in commercial and residential system packs.",
138
- inputSchemaZod: z.object({ segment: z.enum(["commercial", "residential"]).optional() }),
139
- execute: async (args: z.infer<typeof listSystemPacks.inputSchemaZod>): Promise<string> => {
187
+ inputSchemaZod: listSystemPacksSchema,
188
+ execute: async (args: z.infer<typeof listSystemPacksSchema>): Promise<string> => {
140
189
  if (!client.hasApiKey()) return client.noApiKeyMessage();
141
190
  const qs = args.segment ? `?segment=${args.segment}` : "";
142
191
  const data = await client.get(`/estimator/system-packs${qs}`);
@@ -147,12 +196,8 @@ export const listSystemPacks = {
147
196
  export const applySystemPack = {
148
197
  name: "estimator_apply_system_pack",
149
198
  description: "Set system pack slug and optional service profile on takeoff project.",
150
- inputSchemaZod: z.object({
151
- projectId: z.string().uuid(),
152
- systemPackSlug: z.string(),
153
- serviceProfileId: z.string().optional(),
154
- }),
155
- execute: async (args: z.infer<typeof applySystemPack.inputSchemaZod>): Promise<string> => {
199
+ inputSchemaZod: applySystemPackSchema,
200
+ execute: async (args: z.infer<typeof applySystemPackSchema>): Promise<string> => {
156
201
  if (!client.hasApiKey()) return client.noApiKeyMessage();
157
202
  const data = await client.patch(`/estimator/takeoffs/${args.projectId}`, {
158
203
  systemPackSlug: args.systemPackSlug,
@@ -165,8 +210,8 @@ export const applySystemPack = {
165
210
  export const listServiceProfiles = {
166
211
  name: "estimator_list_service_profiles",
167
212
  description: "List service department profiles (includes v6 defaults).",
168
- inputSchemaZod: z.object({}),
169
- execute: async (): Promise<string> => {
213
+ inputSchemaZod: listServiceProfilesSchema,
214
+ execute: async (_args: z.infer<typeof listServiceProfilesSchema>): Promise<string> => {
170
215
  if (!client.hasApiKey()) return client.noApiKeyMessage();
171
216
  const data = await client.get("/estimator/service-profiles");
172
217
  return JSON.stringify(data);
@@ -176,19 +221,8 @@ export const listServiceProfiles = {
176
221
  export const upsertServiceProfile = {
177
222
  name: "estimator_upsert_service_profile",
178
223
  description: "Create or update a service department profile.",
179
- inputSchemaZod: z.object({
180
- slug: z.string(),
181
- name: z.string(),
182
- segment: z.enum([
183
- "commercial_service",
184
- "commercial_production",
185
- "residential_service",
186
- "residential_production",
187
- ]),
188
- laborMultiplier: z.number().optional(),
189
- defaultWastePercent: z.number().optional(),
190
- }),
191
- execute: async (args: z.infer<typeof upsertServiceProfile.inputSchemaZod>): Promise<string> => {
224
+ inputSchemaZod: upsertServiceProfileSchema,
225
+ execute: async (args: z.infer<typeof upsertServiceProfileSchema>): Promise<string> => {
192
226
  if (!client.hasApiKey()) return client.noApiKeyMessage();
193
227
  const data = await client.post("/estimator/service-profiles", args);
194
228
  return JSON.stringify(data);
@@ -210,8 +244,8 @@ export const generateEstimateFromTakeoff = {
210
244
  export const requestEstimateApproval = {
211
245
  name: "estimator_request_estimate_approval",
212
246
  description: "Request owner approval before customer send.",
213
- inputSchemaZod: z.object({ estimateId: z.string().uuid(), note: z.string().optional() }),
214
- execute: async (args: z.infer<typeof requestEstimateApproval.inputSchemaZod>): Promise<string> => {
247
+ inputSchemaZod: requestEstimateApprovalSchema,
248
+ execute: async (args: z.infer<typeof requestEstimateApprovalSchema>): Promise<string> => {
215
249
  if (!client.hasApiKey()) return client.noApiKeyMessage();
216
250
  const data = await client.post(`/estimator/estimates/${args.estimateId}/approval`, {
217
251
  note: args.note,
@@ -223,13 +257,8 @@ export const requestEstimateApproval = {
223
257
  export const approveEstimate = {
224
258
  name: "estimator_approve_estimate",
225
259
  description: "Approve or reject estimate (workspace owner).",
226
- inputSchemaZod: z.object({
227
- estimateId: z.string().uuid(),
228
- approvalId: z.string().uuid(),
229
- action: z.enum(["approve", "reject"]),
230
- note: z.string().optional(),
231
- }),
232
- execute: async (args: z.infer<typeof approveEstimate.inputSchemaZod>): Promise<string> => {
260
+ inputSchemaZod: approveEstimateSchema,
261
+ execute: async (args: z.infer<typeof approveEstimateSchema>): Promise<string> => {
233
262
  if (!client.hasApiKey()) return client.noApiKeyMessage();
234
263
  const data = await client.post(`/estimator/estimates/${args.estimateId}/approval`, {
235
264
  approvalId: args.approvalId,
@@ -243,8 +272,8 @@ export const approveEstimate = {
243
272
  export const getEstimateApproval = {
244
273
  name: "estimator_get_estimate_approval",
245
274
  description: "Get latest approval status for an estimate.",
246
- inputSchemaZod: z.object({ estimateId: z.string().uuid() }),
247
- execute: async (args: z.infer<typeof getEstimateApproval.inputSchemaZod>): Promise<string> => {
275
+ inputSchemaZod: getEstimateApprovalSchema,
276
+ execute: async (args: z.infer<typeof getEstimateApprovalSchema>): Promise<string> => {
248
277
  if (!client.hasApiKey()) return client.noApiKeyMessage();
249
278
  const data = await client.get(`/estimator/estimates/${args.estimateId}/approval`);
250
279
  return JSON.stringify(data);