@kynver-app/mcp-estimator 0.2.7 → 0.2.10
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/server.js +16 -0
- package/dist/tools/catalog-tools.js +1 -1
- package/dist/tools/email-pdf-tools.d.ts +45 -0
- package/dist/tools/email-pdf-tools.js +21 -0
- package/dist/tools/intake-tools.d.ts +463 -0
- package/dist/tools/intake-tools.js +82 -0
- package/dist/tools/price-catalog-tools.d.ts +96 -0
- package/dist/tools/price-catalog-tools.js +72 -0
- package/dist/tools/remodel-deliverable-tools.d.ts +1868 -0
- package/dist/tools/remodel-deliverable-tools.js +138 -0
- package/dist/tools/remodel-estimate-tools.d.ts +525 -0
- package/dist/tools/remodel-estimate-tools.js +71 -0
- package/dist/tools/takeoff-tools.d.ts +274 -0
- package/dist/tools/takeoff-tools.js +38 -4
- package/dist/tools/vision-tools.js +2 -2
- package/package.json +6 -1
- package/src/index.ts +0 -18
- package/src/lib/kynver-client.ts +0 -136
- package/src/lib/mcp-sse-server.ts +0 -249
- package/src/lib/package-version.ts +0 -55
- package/src/server.ts +0 -140
- package/src/sse.ts +0 -10
- package/src/tools/catalog-tools.ts +0 -217
- package/src/tools/client-tools.ts +0 -114
- package/src/tools/company-tools.ts +0 -108
- package/src/tools/email-pdf-tools.ts +0 -27
- package/src/tools/estimate-tools.ts +0 -281
- package/src/tools/intake-tools.ts +0 -95
- package/src/tools/photo-tools.ts +0 -60
- package/src/tools/price-catalog-tools.ts +0 -80
- package/src/tools/remodel-deliverable-tools.ts +0 -72
- package/src/tools/remodel-estimate-tools.ts +0 -80
- package/src/tools/stats-pdf-tools.ts +0 -80
- package/src/tools/takeoff-tools.ts +0 -319
- package/src/tools/template-tools.ts +0 -80
- package/src/tools/version-tools.ts +0 -39
- package/src/tools/vision-tools.ts +0 -48
- package/tsconfig.json +0 -15
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
|
|
4
|
-
// --- estimator_create_replacement ---
|
|
5
|
-
|
|
6
|
-
const createReplacementSchema = z.object({
|
|
7
|
-
projectName: z.string(),
|
|
8
|
-
roofMaterial: z.string(),
|
|
9
|
-
footprintSqFt: z.number().optional(),
|
|
10
|
-
roofAreaSqFt: z.number().optional(),
|
|
11
|
-
pitch: z.string().optional(),
|
|
12
|
-
layers: z.number().optional(),
|
|
13
|
-
ridgeLengthFt: z.number(),
|
|
14
|
-
hipLengthFt: z.number().optional(),
|
|
15
|
-
valleyLengthFt: z.number().optional(),
|
|
16
|
-
eavesFt: z.number(),
|
|
17
|
-
rakeFt: z.number().optional(),
|
|
18
|
-
wastePercent: z.number().optional(),
|
|
19
|
-
clientId: z.string().optional(),
|
|
20
|
-
companyId: z.string().optional(),
|
|
21
|
-
notes: z.string().optional(),
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
export const createReplacement = {
|
|
25
|
-
name: "estimator_create_replacement",
|
|
26
|
-
description:
|
|
27
|
-
"Create a full roof replacement estimate from dimensions. The system calculates line items automatically from the roof measurements using catalog prices. Requires: projectName, roofMaterial (asphalt, metal, tpo, etc.), ridgeLengthFt, eavesFt. Optional: footprintSqFt, roofAreaSqFt, pitch, layers, hipLengthFt, valleyLengthFt, rakeFt, wastePercent. PREREQUISITE: The user must have catalog items set up for the specified roofMaterial — call estimator_get_catalog first to verify.",
|
|
28
|
-
inputSchemaZod: createReplacementSchema,
|
|
29
|
-
execute: async (args: z.infer<typeof createReplacementSchema>): Promise<string> => {
|
|
30
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
31
|
-
const { projectName, clientId, companyId, notes, ...measurements } = args;
|
|
32
|
-
const data = await client.post("/estimator/estimates", {
|
|
33
|
-
type: "replacement",
|
|
34
|
-
projectName,
|
|
35
|
-
roofMaterial: measurements.roofMaterial,
|
|
36
|
-
inputs: measurements,
|
|
37
|
-
clientId,
|
|
38
|
-
companyId,
|
|
39
|
-
notes,
|
|
40
|
-
});
|
|
41
|
-
return JSON.stringify(data);
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// --- estimator_create_repair ---
|
|
46
|
-
|
|
47
|
-
const repairLineSchema = z.object({
|
|
48
|
-
serviceCode: z.string(),
|
|
49
|
-
materialType: z.string().nullable().optional(),
|
|
50
|
-
quantity: z.number(),
|
|
51
|
-
notes: z.string().optional(),
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const createRepairSchema = z.object({
|
|
55
|
-
projectName: z.string(),
|
|
56
|
-
roofMaterial: z.string().optional(),
|
|
57
|
-
lineItems: z.array(repairLineSchema),
|
|
58
|
-
clientId: z.string().optional(),
|
|
59
|
-
companyId: z.string().optional(),
|
|
60
|
-
notes: z.string().optional(),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
export const createRepair = {
|
|
64
|
-
name: "estimator_create_repair",
|
|
65
|
-
description:
|
|
66
|
-
"Create a repair/service estimate with specific line items. Each line item needs a serviceCode that exists in the user's catalog — ALWAYS call estimator_get_catalog first to get valid service codes. Do not guess or invent codes. Pass: projectName, lineItems array (each with serviceCode, quantity, optional materialType and notes). Optionally link to a client/company via clientId/companyId.",
|
|
67
|
-
inputSchemaZod: createRepairSchema,
|
|
68
|
-
execute: async (args: z.infer<typeof createRepairSchema>): Promise<string> => {
|
|
69
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
70
|
-
const data = await client.post("/estimator/estimates", {
|
|
71
|
-
type: "repair_service",
|
|
72
|
-
projectName: args.projectName,
|
|
73
|
-
roofMaterial: args.roofMaterial,
|
|
74
|
-
lineItems: args.lineItems,
|
|
75
|
-
clientId: args.clientId,
|
|
76
|
-
companyId: args.companyId,
|
|
77
|
-
notes: args.notes,
|
|
78
|
-
});
|
|
79
|
-
return JSON.stringify(data);
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// --- estimator_get_estimate ---
|
|
84
|
-
|
|
85
|
-
const getEstimateSchema = z.object({
|
|
86
|
-
id: z.string(),
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
export const getEstimate = {
|
|
90
|
-
name: "estimator_get_estimate",
|
|
91
|
-
description: "Get a specific estimate with all line items, totals (material, labor, grand total), status, client/company info, and metadata. Returns the complete estimate detail needed for viewing, editing, or reporting.",
|
|
92
|
-
inputSchemaZod: getEstimateSchema,
|
|
93
|
-
execute: async (args: z.infer<typeof getEstimateSchema>): Promise<string> => {
|
|
94
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
95
|
-
const data = await client.get<unknown>(`/estimator/estimates/${args.id}`);
|
|
96
|
-
return JSON.stringify(data);
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// --- estimator_list_estimates ---
|
|
101
|
-
|
|
102
|
-
const listEstimatesSchema = z.object({
|
|
103
|
-
status: z.string().optional(),
|
|
104
|
-
type: z.string().optional(),
|
|
105
|
-
clientId: z.string().optional(),
|
|
106
|
-
companyId: z.string().optional(),
|
|
107
|
-
from: z.string().optional(),
|
|
108
|
-
to: z.string().optional(),
|
|
109
|
-
limit: z.number().optional(),
|
|
110
|
-
cursor: z.string().optional(),
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
export const listEstimates = {
|
|
114
|
-
name: "estimator_list_estimates",
|
|
115
|
-
description:
|
|
116
|
-
"List the user's estimates with optional filters. Filter by: status (draft, sent, accepted, declined), type (replacement, repair_service), clientId, companyId, or date range (from/to as ISO strings like 2026-01-01). Returns summary info — use estimator_get_estimate for full details on a specific one.",
|
|
117
|
-
inputSchemaZod: listEstimatesSchema,
|
|
118
|
-
execute: async (args: z.infer<typeof listEstimatesSchema>): Promise<string> => {
|
|
119
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
120
|
-
|
|
121
|
-
const params = new URLSearchParams();
|
|
122
|
-
if (args.status) params.set("status", args.status);
|
|
123
|
-
if (args.type) params.set("type", args.type);
|
|
124
|
-
if (args.clientId) params.set("clientId", args.clientId);
|
|
125
|
-
if (args.companyId) params.set("companyId", args.companyId);
|
|
126
|
-
if (args.from) params.set("from", args.from);
|
|
127
|
-
if (args.to) params.set("to", args.to);
|
|
128
|
-
if (args.limit != null) params.set("limit", String(args.limit));
|
|
129
|
-
if (args.cursor) params.set("cursor", args.cursor);
|
|
130
|
-
|
|
131
|
-
const q = params.toString();
|
|
132
|
-
const path = q ? `/estimator/estimates?${q}` : "/estimator/estimates";
|
|
133
|
-
|
|
134
|
-
const data = await client.get<unknown>(path);
|
|
135
|
-
return JSON.stringify(data);
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// --- estimator_add_line_item ---
|
|
140
|
-
|
|
141
|
-
const addLineItemSchema = z.object({
|
|
142
|
-
estimateId: z.string(),
|
|
143
|
-
serviceCode: z.string(),
|
|
144
|
-
materialType: z.string().nullable().optional(),
|
|
145
|
-
quantity: z.number(),
|
|
146
|
-
notes: z.string().optional(),
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
export const addLineItem = {
|
|
150
|
-
name: "estimator_add_line_item",
|
|
151
|
-
description: "Add a line item to an existing estimate. The serviceCode must exist in the user's catalog — call estimator_get_catalog to verify. Pass: estimateId, serviceCode, quantity. Optionally add notes. The estimate totals are automatically recalculated.",
|
|
152
|
-
inputSchemaZod: addLineItemSchema,
|
|
153
|
-
execute: async (args: z.infer<typeof addLineItemSchema>): Promise<string> => {
|
|
154
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
155
|
-
const { estimateId, ...body } = args;
|
|
156
|
-
const data = await client.post(`/estimator/estimates/${estimateId}/line-items`, body);
|
|
157
|
-
return JSON.stringify(data);
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// --- estimator_remove_line_item ---
|
|
162
|
-
|
|
163
|
-
const removeLineItemSchema = z.object({
|
|
164
|
-
estimateId: z.string(),
|
|
165
|
-
lineItemId: z.string(),
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
export const removeLineItem = {
|
|
169
|
-
name: "estimator_remove_line_item",
|
|
170
|
-
description: "Remove a line item from an estimate. Requires estimateId and lineItemId (get lineItemIds from estimator_get_estimate). The estimate totals are automatically recalculated.",
|
|
171
|
-
inputSchemaZod: removeLineItemSchema,
|
|
172
|
-
execute: async (args: z.infer<typeof removeLineItemSchema>): Promise<string> => {
|
|
173
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
174
|
-
const data = await client.del(`/estimator/estimates/${args.estimateId}/line-items/${args.lineItemId}`);
|
|
175
|
-
return JSON.stringify(data);
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// --- estimator_update_line_item ---
|
|
180
|
-
|
|
181
|
-
const updateLineItemSchema = z.object({
|
|
182
|
-
estimateId: z.string(),
|
|
183
|
-
lineItemId: z.string(),
|
|
184
|
-
quantity: z.number().optional(),
|
|
185
|
-
materialCost: z.number().optional(),
|
|
186
|
-
laborCost: z.number().optional(),
|
|
187
|
-
notes: z.string().optional(),
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
export const updateLineItem = {
|
|
191
|
-
name: "estimator_update_line_item",
|
|
192
|
-
description: "Update a line item's quantity, materialCost, laborCost, or notes on an existing estimate. Requires estimateId and lineItemId. Totals are recalculated automatically after the update.",
|
|
193
|
-
inputSchemaZod: updateLineItemSchema,
|
|
194
|
-
execute: async (args: z.infer<typeof updateLineItemSchema>): Promise<string> => {
|
|
195
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
196
|
-
const { estimateId, lineItemId, ...body } = args;
|
|
197
|
-
const data = await client.patch(`/estimator/estimates/${estimateId}/line-items/${lineItemId}`, body);
|
|
198
|
-
return JSON.stringify(data);
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// --- estimator_adjust_to_target ---
|
|
203
|
-
|
|
204
|
-
const adjustToTargetSchema = z.object({
|
|
205
|
-
estimateId: z.string(),
|
|
206
|
-
targetTotal: z.number(),
|
|
207
|
-
strategy: z.enum(["uniform", "labor_only", "material_only"]).optional(),
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
export const adjustToTarget = {
|
|
211
|
-
name: "estimator_adjust_to_target",
|
|
212
|
-
description:
|
|
213
|
-
"Adjust all line items on an estimate to hit a specific target total price. Useful when a customer has a budget and you need to scale pricing to fit. Strategies: 'uniform' (default — scales all items proportionally), 'labor_only' (only adjusts labor costs), 'material_only' (only adjusts material costs). Requires estimateId and targetTotal.",
|
|
214
|
-
inputSchemaZod: adjustToTargetSchema,
|
|
215
|
-
execute: async (args: z.infer<typeof adjustToTargetSchema>): Promise<string> => {
|
|
216
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
217
|
-
const { estimateId, ...body } = args;
|
|
218
|
-
const data = await client.post(`/estimator/estimates/${estimateId}/adjust`, body);
|
|
219
|
-
return JSON.stringify(data);
|
|
220
|
-
},
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// --- estimator_update_estimate ---
|
|
224
|
-
|
|
225
|
-
const updateEstimateSchema = z.object({
|
|
226
|
-
estimateId: z.string(),
|
|
227
|
-
projectName: z.string().optional(),
|
|
228
|
-
status: z.string().optional(),
|
|
229
|
-
clientId: z.string().optional(),
|
|
230
|
-
companyId: z.string().optional(),
|
|
231
|
-
notes: z.string().optional(),
|
|
232
|
-
siteAddress: z.string().optional(),
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
export const updateEstimate = {
|
|
236
|
-
name: "estimator_update_estimate",
|
|
237
|
-
description: "Update an estimate's metadata — project name, status (draft/sent/accepted/declined), site address, client/company linking, or notes. Does NOT modify line items (use add/remove/update line item tools for that). Pass estimateId and any fields to change.",
|
|
238
|
-
inputSchemaZod: updateEstimateSchema,
|
|
239
|
-
execute: async (args: z.infer<typeof updateEstimateSchema>): Promise<string> => {
|
|
240
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
241
|
-
const { estimateId, ...body } = args;
|
|
242
|
-
const data = await client.patch(`/estimator/estimates/${estimateId}`, body);
|
|
243
|
-
return JSON.stringify(data);
|
|
244
|
-
},
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// --- estimator_delete_estimate ---
|
|
248
|
-
|
|
249
|
-
const deleteEstimateSchema = z.object({
|
|
250
|
-
estimateId: z.string(),
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
export const deleteEstimate = {
|
|
254
|
-
name: "estimator_delete_estimate",
|
|
255
|
-
description: "Soft-delete an estimate. The estimate data is preserved but hidden from list views. This cannot be easily undone.",
|
|
256
|
-
inputSchemaZod: deleteEstimateSchema,
|
|
257
|
-
execute: async (args: z.infer<typeof deleteEstimateSchema>): Promise<string> => {
|
|
258
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
259
|
-
const data = await client.del(`/estimator/estimates/${args.estimateId}`);
|
|
260
|
-
return JSON.stringify(data);
|
|
261
|
-
},
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
// --- estimator_duplicate_estimate ---
|
|
265
|
-
|
|
266
|
-
const duplicateEstimateSchema = z.object({
|
|
267
|
-
estimateId: z.string(),
|
|
268
|
-
projectName: z.string().optional(),
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
export const duplicateEstimate = {
|
|
272
|
-
name: "estimator_duplicate_estimate",
|
|
273
|
-
description: "Create a copy of an existing estimate with all its line items, pricing, and configuration. Optionally give the copy a new projectName. Useful for creating similar estimates for different clients or properties.",
|
|
274
|
-
inputSchemaZod: duplicateEstimateSchema,
|
|
275
|
-
execute: async (args: z.infer<typeof duplicateEstimateSchema>): Promise<string> => {
|
|
276
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
277
|
-
const { estimateId, ...body } = args;
|
|
278
|
-
const data = await client.post(`/estimator/estimates/${estimateId}/duplicate`, body);
|
|
279
|
-
return JSON.stringify(data);
|
|
280
|
-
},
|
|
281
|
-
};
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
|
|
4
|
-
const remodelProjectTypeSchema = z.enum([
|
|
5
|
-
"kitchen_remodel",
|
|
6
|
-
"bath_remodel",
|
|
7
|
-
"kitchen_and_bath_remodel",
|
|
8
|
-
"whole_home_remodel",
|
|
9
|
-
"other_remodel",
|
|
10
|
-
]);
|
|
11
|
-
|
|
12
|
-
const normalizeRemodelIntakeSchema = z.object({
|
|
13
|
-
projectType: remodelProjectTypeSchema,
|
|
14
|
-
projectName: z.string().optional(),
|
|
15
|
-
siteAddress: z.string().optional(),
|
|
16
|
-
baseScopes: z
|
|
17
|
-
.array(
|
|
18
|
-
z.object({
|
|
19
|
-
key: z.string().optional(),
|
|
20
|
-
label: z.string(),
|
|
21
|
-
quantity: z.number().optional(),
|
|
22
|
-
unit: z.string().optional(),
|
|
23
|
-
serviceCode: z.string().optional(),
|
|
24
|
-
budgetLump: z.number().optional(),
|
|
25
|
-
notes: z.string().optional(),
|
|
26
|
-
confidence: z.enum(["high", "medium", "low", "unknown"]).optional(),
|
|
27
|
-
isAlternate: z.boolean().optional(),
|
|
28
|
-
alternateGroup: z.string().optional(),
|
|
29
|
-
})
|
|
30
|
-
)
|
|
31
|
-
.optional(),
|
|
32
|
-
alternates: z
|
|
33
|
-
.array(
|
|
34
|
-
z.object({
|
|
35
|
-
key: z.string(),
|
|
36
|
-
label: z.string(),
|
|
37
|
-
scopeKeys: z.array(z.string()).optional(),
|
|
38
|
-
lumpAdd: z.number().optional(),
|
|
39
|
-
notes: z.string().optional(),
|
|
40
|
-
})
|
|
41
|
-
)
|
|
42
|
-
.optional(),
|
|
43
|
-
attachments: z
|
|
44
|
-
.array(
|
|
45
|
-
z.object({
|
|
46
|
-
id: z.string(),
|
|
47
|
-
kind: z.enum(["photo", "drawing", "plan_set", "spec_pdf", "spreadsheet", "other"]),
|
|
48
|
-
fileName: z.string().optional(),
|
|
49
|
-
caption: z.string().optional(),
|
|
50
|
-
url: z.string().optional(),
|
|
51
|
-
mimeType: z.string().optional(),
|
|
52
|
-
})
|
|
53
|
-
)
|
|
54
|
-
.optional(),
|
|
55
|
-
exclusions: z.array(z.string()).optional(),
|
|
56
|
-
surprises: z.array(z.string()).optional(),
|
|
57
|
-
rateReferences: z
|
|
58
|
-
.array(
|
|
59
|
-
z.object({
|
|
60
|
-
key: z.string(),
|
|
61
|
-
label: z.string(),
|
|
62
|
-
value: z.number(),
|
|
63
|
-
unit: z.string().optional(),
|
|
64
|
-
source: z.enum(["operating_rule", "catalog", "user", "takeoff"]),
|
|
65
|
-
catalogId: z.string().optional(),
|
|
66
|
-
confidence: z.enum(["high", "medium", "low", "unknown"]).optional(),
|
|
67
|
-
})
|
|
68
|
-
)
|
|
69
|
-
.optional(),
|
|
70
|
-
assumptionsNeedingReview: z
|
|
71
|
-
.array(
|
|
72
|
-
z.object({
|
|
73
|
-
id: z.string(),
|
|
74
|
-
text: z.string(),
|
|
75
|
-
severity: z.enum(["info", "warn", "blocker"]),
|
|
76
|
-
relatedScopeKey: z.string().optional(),
|
|
77
|
-
})
|
|
78
|
-
)
|
|
79
|
-
.optional(),
|
|
80
|
-
remodelHints: z.record(z.string(), z.unknown()).optional(),
|
|
81
|
-
includeStandardAlternates: z.boolean().optional(),
|
|
82
|
-
notes: z.string().optional(),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
export const normalizeRemodelIntake = {
|
|
86
|
-
name: "estimator_normalize_remodel_intake",
|
|
87
|
-
description:
|
|
88
|
-
"Validate and normalize a residential remodel intake payload for kitchen/bath estimating. Accepts project type, base scopes vs alternates, drawing/photo attachment metadata, exclusions/surprises, rate references, and optional remodelHints from takeoff. Returns provenance-backed scopes, Will Portland default rates, assumptions needing review, and an estimate-builder-ready payload.",
|
|
89
|
-
inputSchemaZod: normalizeRemodelIntakeSchema,
|
|
90
|
-
execute: async (args: z.infer<typeof normalizeRemodelIntakeSchema>): Promise<string> => {
|
|
91
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
92
|
-
const data = await client.post("/estimator/intake/normalize-remodel", args);
|
|
93
|
-
return JSON.stringify(data, null, 2);
|
|
94
|
-
},
|
|
95
|
-
};
|
package/src/tools/photo-tools.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
|
|
4
|
-
// --- estimator_upload_photo ---
|
|
5
|
-
|
|
6
|
-
const uploadPhotoSchema = z.object({
|
|
7
|
-
estimateId: z.string(),
|
|
8
|
-
imageUrl: z.string(),
|
|
9
|
-
caption: z.string().optional(),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
export const uploadPhoto = {
|
|
13
|
-
name: "estimator_upload_photo",
|
|
14
|
-
description:
|
|
15
|
-
"Attach a photo to an estimate by URL. The photo is downloaded and stored. Include a caption to describe what the photo shows (e.g., 'North-facing roof slope showing damaged shingles'). Photos can be included in PDF exports and analyzed with estimator_analyze_photos.",
|
|
16
|
-
inputSchemaZod: uploadPhotoSchema,
|
|
17
|
-
execute: async (args: z.infer<typeof uploadPhotoSchema>): Promise<string> => {
|
|
18
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
19
|
-
const { estimateId, ...body } = args;
|
|
20
|
-
const data = await client.post(`/estimator/estimates/${estimateId}/photos`, body);
|
|
21
|
-
return JSON.stringify(data);
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// --- estimator_list_photos ---
|
|
26
|
-
|
|
27
|
-
const listPhotosSchema = z.object({
|
|
28
|
-
estimateId: z.string(),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
export const listPhotos = {
|
|
32
|
-
name: "estimator_list_photos",
|
|
33
|
-
description: "List all photos attached to an estimate. Returns photo IDs, URLs, captions, and sort order. Use photo IDs when deleting photos or configuring PDF output.",
|
|
34
|
-
inputSchemaZod: listPhotosSchema,
|
|
35
|
-
execute: async (args: z.infer<typeof listPhotosSchema>): Promise<string> => {
|
|
36
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
37
|
-
const data = await client.get<unknown>(`/estimator/estimates/${args.estimateId}/photos`);
|
|
38
|
-
return JSON.stringify(data);
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// --- estimator_delete_photo ---
|
|
43
|
-
|
|
44
|
-
const deletePhotoSchema = z.object({
|
|
45
|
-
estimateId: z.string(),
|
|
46
|
-
photoId: z.string(),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export const deletePhoto = {
|
|
50
|
-
name: "estimator_delete_photo",
|
|
51
|
-
description: "Remove a photo from an estimate. Requires estimateId and photoId (get IDs from estimator_list_photos).",
|
|
52
|
-
inputSchemaZod: deletePhotoSchema,
|
|
53
|
-
execute: async (args: z.infer<typeof deletePhotoSchema>): Promise<string> => {
|
|
54
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
55
|
-
const data = await client.del(
|
|
56
|
-
`/estimator/estimates/${args.estimateId}/photos/${args.photoId}`
|
|
57
|
-
);
|
|
58
|
-
return JSON.stringify(data);
|
|
59
|
-
},
|
|
60
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
|
|
4
|
-
const listPriceCatalogsSchema = z.object({});
|
|
5
|
-
|
|
6
|
-
export const listPriceCatalogs = {
|
|
7
|
-
name: "estimator_list_price_catalogs",
|
|
8
|
-
description:
|
|
9
|
-
"List durable builtin price catalogs shipped with the estimator (region, effective date, markup policy). These are the MCP's stored price lists — not AgentOS memory. Use before seeding or resolving remodel defaults.",
|
|
10
|
-
inputSchemaZod: listPriceCatalogsSchema,
|
|
11
|
-
execute: async (_args: z.infer<typeof listPriceCatalogsSchema>): Promise<string> => {
|
|
12
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
13
|
-
const data = await client.get("/estimator/price-catalog");
|
|
14
|
-
return JSON.stringify(data);
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const getPriceCatalogSchema = z.object({
|
|
19
|
-
catalogId: z.string(),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export const getPriceCatalog = {
|
|
23
|
-
name: "estimator_get_price_catalog",
|
|
24
|
-
description:
|
|
25
|
-
"Read all unit-price lines from a builtin price catalog (service codes, units, material/labor split, review/confidence metadata). Default Portland remodel catalog id: portland-remodel-will-2026-06.",
|
|
26
|
-
inputSchemaZod: getPriceCatalogSchema,
|
|
27
|
-
execute: async (args: z.infer<typeof getPriceCatalogSchema>): Promise<string> => {
|
|
28
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
29
|
-
const data = await client.get(`/estimator/price-catalog/${encodeURIComponent(args.catalogId)}`);
|
|
30
|
-
return JSON.stringify(data);
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const resolvePriceSchema = z.object({
|
|
35
|
-
serviceCode: z.string(),
|
|
36
|
-
materialType: z.string().nullable().optional(),
|
|
37
|
-
region: z.string().optional(),
|
|
38
|
-
overrideMaterialCost: z.number().min(0).optional(),
|
|
39
|
-
overrideLaborCost: z.number().min(0).optional(),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
export const resolvePrice = {
|
|
43
|
-
name: "estimator_resolve_price",
|
|
44
|
-
description:
|
|
45
|
-
"Resolve unit pricing for one service code with provenance: user price book → builtin catalog → optional intake override (overrideMaterialCost/overrideLaborCost). Returns source, region, effective date, and markupIncluded flag.",
|
|
46
|
-
inputSchemaZod: resolvePriceSchema,
|
|
47
|
-
execute: async (args: z.infer<typeof resolvePriceSchema>): Promise<string> => {
|
|
48
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
49
|
-
|
|
50
|
-
const params = new URLSearchParams({ serviceCode: args.serviceCode });
|
|
51
|
-
if (args.materialType) params.set("materialType", args.materialType);
|
|
52
|
-
if (args.region) params.set("region", args.region);
|
|
53
|
-
if (args.overrideMaterialCost != null) {
|
|
54
|
-
params.set("overrideMaterialCost", String(args.overrideMaterialCost));
|
|
55
|
-
}
|
|
56
|
-
if (args.overrideLaborCost != null) {
|
|
57
|
-
params.set("overrideLaborCost", String(args.overrideLaborCost));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const data = await client.get(`/estimator/price-catalog/resolve?${params.toString()}`);
|
|
61
|
-
return JSON.stringify(data);
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const seedPriceCatalogSchema = z.object({
|
|
66
|
-
catalogId: z.string(),
|
|
67
|
-
overwrite: z.boolean().optional(),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
export const seedPriceCatalog = {
|
|
71
|
-
name: "estimator_seed_price_catalog",
|
|
72
|
-
description:
|
|
73
|
-
"Copy a builtin price catalog into the user's ServiceCatalogItem price book (skips existing codes unless overwrite=true). Use portland-remodel-will-2026-06 for Will's Portland remodel working rates.",
|
|
74
|
-
inputSchemaZod: seedPriceCatalogSchema,
|
|
75
|
-
execute: async (args: z.infer<typeof seedPriceCatalogSchema>): Promise<string> => {
|
|
76
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
77
|
-
const data = await client.post("/estimator/price-catalog/seed", args);
|
|
78
|
-
return JSON.stringify(data);
|
|
79
|
-
},
|
|
80
|
-
};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
|
|
4
|
-
const packageSchema = z.object({
|
|
5
|
-
intake: z.object({
|
|
6
|
-
projectName: z.string().optional(),
|
|
7
|
-
scopes: z.array(
|
|
8
|
-
z.object({
|
|
9
|
-
key: z.string(),
|
|
10
|
-
label: z.string().optional(),
|
|
11
|
-
quantity: z.number().optional(),
|
|
12
|
-
unit: z.string().optional(),
|
|
13
|
-
serviceCode: z.string().optional(),
|
|
14
|
-
budgetLump: z.number().optional(),
|
|
15
|
-
notes: z.string().optional(),
|
|
16
|
-
})
|
|
17
|
-
),
|
|
18
|
-
expandScopeComponents: z.boolean().optional(),
|
|
19
|
-
includeStandardAlternates: z.boolean().optional(),
|
|
20
|
-
exclusions: z.array(z.string()).optional(),
|
|
21
|
-
assumptions: z.array(z.string()).optional(),
|
|
22
|
-
notes: z.string().optional(),
|
|
23
|
-
}),
|
|
24
|
-
pricing: z
|
|
25
|
-
.object({
|
|
26
|
-
contingencyPercent: z.number().optional(),
|
|
27
|
-
markupPercent: z.number().optional(),
|
|
28
|
-
profitPercent: z.number().optional(),
|
|
29
|
-
})
|
|
30
|
-
.optional(),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const buildDeliverableSchema = z.object({
|
|
34
|
-
preset: z.enum(["matthew-portland-2026-06"]).optional(),
|
|
35
|
-
client: z.object({
|
|
36
|
-
projectTitle: z.string(),
|
|
37
|
-
clientName: z.string().optional(),
|
|
38
|
-
preparedBy: z.string().optional(),
|
|
39
|
-
location: z.string().optional(),
|
|
40
|
-
documentType: z.string().optional(),
|
|
41
|
-
validityNote: z.string().optional(),
|
|
42
|
-
}),
|
|
43
|
-
budgetPackage: packageSchema.optional(),
|
|
44
|
-
fullPackage: packageSchema.optional(),
|
|
45
|
-
alternates: z
|
|
46
|
-
.array(
|
|
47
|
-
z.object({
|
|
48
|
-
key: z.string(),
|
|
49
|
-
label: z.string(),
|
|
50
|
-
description: z.string(),
|
|
51
|
-
budgetPlaceholder: z.number().nullable(),
|
|
52
|
-
})
|
|
53
|
-
)
|
|
54
|
-
.optional(),
|
|
55
|
-
correctionNote: z.string().optional(),
|
|
56
|
-
scopeBasis: z.array(z.string()).optional(),
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
export const buildRemodelDeliverable = {
|
|
60
|
-
name: "estimator_build_remodel_deliverable",
|
|
61
|
-
description:
|
|
62
|
-
"Build a dogfoodable residential remodel deliverable package with distinct budget/base and full/dream scope sections, separate alternates (never in headline totals), client-facing line-item descriptions, HTML/PDF handoff metadata, and MCP tool guidance for deeper detail. Use preset matthew-portland-2026-06 for the Matthew Portland fixture ($339,025 full / $167,200 bank-budget). Does not persist — use handoff JSON + reviewMarkdown to render client PDF/HTML. Supersedes hand-built deliverables that omitted structured sections. For single-scope preview only, use estimator_build_remodel_estimate.",
|
|
63
|
-
inputSchemaZod: buildDeliverableSchema,
|
|
64
|
-
execute: async (args: z.infer<typeof buildDeliverableSchema>): Promise<string> => {
|
|
65
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
66
|
-
const data = await client.post(
|
|
67
|
-
"/estimator/estimates/build-remodel-deliverable",
|
|
68
|
-
args
|
|
69
|
-
);
|
|
70
|
-
return JSON.stringify(data);
|
|
71
|
-
},
|
|
72
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
|
|
4
|
-
const scopeSchema = z.object({
|
|
5
|
-
key: z.string(),
|
|
6
|
-
label: z.string().optional(),
|
|
7
|
-
quantity: z.number().optional(),
|
|
8
|
-
unit: z.string().optional(),
|
|
9
|
-
serviceCode: z.string().optional(),
|
|
10
|
-
budgetLump: z.number().optional(),
|
|
11
|
-
notes: z.string().optional(),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
const buildRemodelSchema = z.object({
|
|
15
|
-
intake: z.object({
|
|
16
|
-
projectName: z.string().optional(),
|
|
17
|
-
scopes: z.array(scopeSchema),
|
|
18
|
-
alternates: z
|
|
19
|
-
.array(
|
|
20
|
-
z.object({
|
|
21
|
-
key: z.string(),
|
|
22
|
-
label: z.string(),
|
|
23
|
-
scopeKeys: z.array(z.string()).optional(),
|
|
24
|
-
lumpAdd: z.number().optional(),
|
|
25
|
-
notes: z.string().optional(),
|
|
26
|
-
})
|
|
27
|
-
)
|
|
28
|
-
.optional(),
|
|
29
|
-
includeStandardAlternates: z.boolean().optional(),
|
|
30
|
-
expandScopeComponents: z.boolean().optional(),
|
|
31
|
-
exclusions: z.array(z.string()).optional(),
|
|
32
|
-
assumptions: z.array(z.string()).optional(),
|
|
33
|
-
notes: z.string().optional(),
|
|
34
|
-
}),
|
|
35
|
-
takeoffRows: z
|
|
36
|
-
.array(
|
|
37
|
-
z.object({
|
|
38
|
-
id: z.string().optional(),
|
|
39
|
-
description: z.string(),
|
|
40
|
-
quantity: z.number(),
|
|
41
|
-
unit: z.string(),
|
|
42
|
-
serviceCode: z.string().nullable().optional(),
|
|
43
|
-
materialType: z.string().nullable().optional(),
|
|
44
|
-
})
|
|
45
|
-
)
|
|
46
|
-
.optional(),
|
|
47
|
-
overrides: z
|
|
48
|
-
.array(
|
|
49
|
-
z.object({
|
|
50
|
-
scopeKey: z.string().optional(),
|
|
51
|
-
serviceCode: z.string().optional(),
|
|
52
|
-
description: z.string().optional(),
|
|
53
|
-
quantity: z.number().optional(),
|
|
54
|
-
unitMaterial: z.number().optional(),
|
|
55
|
-
unitLabor: z.number().optional(),
|
|
56
|
-
lumpTotal: z.number().optional(),
|
|
57
|
-
notes: z.string().optional(),
|
|
58
|
-
})
|
|
59
|
-
)
|
|
60
|
-
.optional(),
|
|
61
|
-
pricing: z
|
|
62
|
-
.object({
|
|
63
|
-
contingencyPercent: z.number().optional(),
|
|
64
|
-
markupPercent: z.number().optional(),
|
|
65
|
-
profitPercent: z.number().optional(),
|
|
66
|
-
})
|
|
67
|
-
.optional(),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
export const buildRemodelEstimate = {
|
|
71
|
-
name: "estimator_build_remodel_estimate",
|
|
72
|
-
description:
|
|
73
|
-
"Build a reviewable residential remodel estimate from normalized intake scopes, optional takeoff quantities, catalog prices, and overrides. Lump room scopes (kitchen, baths) auto-expand into a per-trade breakdown of line items (demolition, cabinetry, countertops, plumbing, electrical, appliances, flooring, tile, finishes) — each with its own scope-of-work description carried in notes — so client-facing output is itemized, not a single vague lump (set intake.expandScopeComponents=false for the legacy single-line behavior). Returns line items with provenance, alternates (both baths, upstairs expansion, roof, wall moving), a default+custom assumptions list, default+custom exclusions, contingency/markup/profit breakdown, and reviewMarkdown. Pass project-specific items via intake.assumptions and intake.exclusions. Does not persist — use estimator_generate_estimate_from_takeoff or estimator_create_repair after review. Room scope keys: kitchen, bath_full, bath_half, bath_primary, bath_secondary. Trade/quantity scope keys: cabinets, drywall, paint, framing, roof, flooring, electrical_kitchen, electrical_bath. Whole-project phase scope keys: general_conditions, permits, demolition, protection, final_clean.",
|
|
74
|
-
inputSchemaZod: buildRemodelSchema,
|
|
75
|
-
execute: async (args: z.infer<typeof buildRemodelSchema>): Promise<string> => {
|
|
76
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
77
|
-
const data = await client.post("/estimator/estimates/build-remodel", args);
|
|
78
|
-
return JSON.stringify(data);
|
|
79
|
-
},
|
|
80
|
-
};
|