@kynver-app/mcp-estimator 0.2.8 → 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 -141
- 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 -163
- 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,163 +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. Prefer estimator_draft_remodel_deliverable when you have scope notes and want the tool to assemble the budget/full intakes for you.",
|
|
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
|
-
};
|
|
73
|
-
|
|
74
|
-
const draftScopeSchema = z.object({
|
|
75
|
-
key: z.string().min(1),
|
|
76
|
-
label: z.string().optional(),
|
|
77
|
-
quantity: z.number().positive().optional(),
|
|
78
|
-
unit: z.string().optional(),
|
|
79
|
-
serviceCode: z.string().optional(),
|
|
80
|
-
budgetLump: z.number().nonnegative().optional(),
|
|
81
|
-
notes: z.string().optional(),
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const draftPricingSchema = z
|
|
85
|
-
.object({
|
|
86
|
-
contingencyPercent: z.number().optional(),
|
|
87
|
-
markupPercent: z.number().optional(),
|
|
88
|
-
profitPercent: z.number().optional(),
|
|
89
|
-
})
|
|
90
|
-
.optional();
|
|
91
|
-
|
|
92
|
-
const draftScopeBundleSchema = z.object({
|
|
93
|
-
label: z.string().optional(),
|
|
94
|
-
whatsIncluded: z.string().optional(),
|
|
95
|
-
projectName: z.string().optional(),
|
|
96
|
-
scopes: z.array(draftScopeSchema),
|
|
97
|
-
assumptions: z.array(z.string()).optional(),
|
|
98
|
-
exclusions: z.array(z.string()).optional(),
|
|
99
|
-
notes: z.string().optional(),
|
|
100
|
-
expandScopeComponents: z.boolean().optional(),
|
|
101
|
-
pricing: draftPricingSchema,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const draftBudgetBundleSchema = draftScopeBundleSchema.extend({
|
|
105
|
-
derivedFromFull: z
|
|
106
|
-
.object({
|
|
107
|
-
omitFromFullKeys: z.array(z.string()).optional(),
|
|
108
|
-
replaceWithAllowanceLump: z
|
|
109
|
-
.array(
|
|
110
|
-
z.object({
|
|
111
|
-
key: z.string().min(1),
|
|
112
|
-
label: z.string().optional(),
|
|
113
|
-
budgetLump: z.number().nonnegative(),
|
|
114
|
-
notes: z.string().optional(),
|
|
115
|
-
})
|
|
116
|
-
)
|
|
117
|
-
.optional(),
|
|
118
|
-
additionalScopes: z.array(draftScopeSchema).optional(),
|
|
119
|
-
})
|
|
120
|
-
.optional(),
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const draftDeliverableSchema = z.object({
|
|
124
|
-
client: z.object({
|
|
125
|
-
projectTitle: z.string().min(1),
|
|
126
|
-
clientName: z.string().optional(),
|
|
127
|
-
preparedBy: z.string().optional(),
|
|
128
|
-
location: z.string().optional(),
|
|
129
|
-
documentType: z.string().optional(),
|
|
130
|
-
validityNote: z.string().optional(),
|
|
131
|
-
}),
|
|
132
|
-
fullScope: draftScopeBundleSchema,
|
|
133
|
-
budgetScope: draftBudgetBundleSchema.optional(),
|
|
134
|
-
alternates: z
|
|
135
|
-
.array(
|
|
136
|
-
z.object({
|
|
137
|
-
key: z.string(),
|
|
138
|
-
label: z.string(),
|
|
139
|
-
description: z.string(),
|
|
140
|
-
budgetPlaceholder: z.number().nullable(),
|
|
141
|
-
})
|
|
142
|
-
)
|
|
143
|
-
.optional(),
|
|
144
|
-
correctionNote: z.string().optional(),
|
|
145
|
-
scopeBasis: z.array(z.string()).optional(),
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
export const draftRemodelDeliverable = {
|
|
149
|
-
name: "estimator_draft_remodel_deliverable",
|
|
150
|
-
description:
|
|
151
|
-
"Draft a sendable residential remodel deliverable package from scope notes in a single call. Provide one fullScope.scopes list (each scope is a key like 'kitchen', 'bath_primary', 'drywall', or a custom slug, plus optional budgetLump/notes — the built-in catalog supplies label/unit/default rate). The budget package can be (a) provided explicitly via budgetScope.scopes, (b) derived from full via budgetScope.derivedFromFull.omitFromFullKeys (drop scopes from full), and/or budgetScope.derivedFromFull.replaceWithAllowanceLump (collapse component-detail rooms into single allowance lines), or (c) omitted to mirror full as collapsed allowances. Returns the full RemodelDeliverablePackage including budgetScope, fullScope, alternates, allowances, assumptions, exclusions, handoff metadata, reviewMarkdown (agent-facing), and clientMarkdown (sendable). Does not persist. Use this for fresh remodel intakes instead of estimator_build_remodel_deliverable when you have not yet authored both intakes by hand.",
|
|
152
|
-
inputSchemaZod: draftDeliverableSchema,
|
|
153
|
-
execute: async (
|
|
154
|
-
args: z.infer<typeof draftDeliverableSchema>
|
|
155
|
-
): Promise<string> => {
|
|
156
|
-
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
157
|
-
const data = await client.post(
|
|
158
|
-
"/estimator/estimates/draft-remodel-deliverable",
|
|
159
|
-
args
|
|
160
|
-
);
|
|
161
|
-
return JSON.stringify(data);
|
|
162
|
-
},
|
|
163
|
-
};
|