@kynver-app/mcp-estimator 0.2.8 → 0.2.11
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/lib/mcp-sse-server.d.ts +15 -0
- package/dist/lib/mcp-sse-server.js +103 -2
- 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
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as client from "../lib/kynver-client.js";
|
|
3
|
+
const scopeSchema = z.object({
|
|
4
|
+
key: z.string(),
|
|
5
|
+
label: z.string().optional(),
|
|
6
|
+
quantity: z.number().optional(),
|
|
7
|
+
unit: z.string().optional(),
|
|
8
|
+
serviceCode: z.string().optional(),
|
|
9
|
+
budgetLump: z.number().optional(),
|
|
10
|
+
notes: z.string().optional(),
|
|
11
|
+
});
|
|
12
|
+
const buildRemodelSchema = z.object({
|
|
13
|
+
intake: z.object({
|
|
14
|
+
projectName: z.string().optional(),
|
|
15
|
+
scopes: z.array(scopeSchema),
|
|
16
|
+
alternates: z
|
|
17
|
+
.array(z.object({
|
|
18
|
+
key: z.string(),
|
|
19
|
+
label: z.string(),
|
|
20
|
+
scopeKeys: z.array(z.string()).optional(),
|
|
21
|
+
lumpAdd: z.number().optional(),
|
|
22
|
+
notes: z.string().optional(),
|
|
23
|
+
}))
|
|
24
|
+
.optional(),
|
|
25
|
+
includeStandardAlternates: z.boolean().optional(),
|
|
26
|
+
expandScopeComponents: z.boolean().optional(),
|
|
27
|
+
exclusions: z.array(z.string()).optional(),
|
|
28
|
+
assumptions: z.array(z.string()).optional(),
|
|
29
|
+
notes: z.string().optional(),
|
|
30
|
+
}),
|
|
31
|
+
takeoffRows: z
|
|
32
|
+
.array(z.object({
|
|
33
|
+
id: z.string().optional(),
|
|
34
|
+
description: z.string(),
|
|
35
|
+
quantity: z.number(),
|
|
36
|
+
unit: z.string(),
|
|
37
|
+
serviceCode: z.string().nullable().optional(),
|
|
38
|
+
materialType: z.string().nullable().optional(),
|
|
39
|
+
}))
|
|
40
|
+
.optional(),
|
|
41
|
+
overrides: z
|
|
42
|
+
.array(z.object({
|
|
43
|
+
scopeKey: z.string().optional(),
|
|
44
|
+
serviceCode: z.string().optional(),
|
|
45
|
+
description: z.string().optional(),
|
|
46
|
+
quantity: z.number().optional(),
|
|
47
|
+
unitMaterial: z.number().optional(),
|
|
48
|
+
unitLabor: z.number().optional(),
|
|
49
|
+
lumpTotal: z.number().optional(),
|
|
50
|
+
notes: z.string().optional(),
|
|
51
|
+
}))
|
|
52
|
+
.optional(),
|
|
53
|
+
pricing: z
|
|
54
|
+
.object({
|
|
55
|
+
contingencyPercent: z.number().optional(),
|
|
56
|
+
markupPercent: z.number().optional(),
|
|
57
|
+
profitPercent: z.number().optional(),
|
|
58
|
+
})
|
|
59
|
+
.optional(),
|
|
60
|
+
});
|
|
61
|
+
export const buildRemodelEstimate = {
|
|
62
|
+
name: "estimator_build_remodel_estimate",
|
|
63
|
+
description: "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.",
|
|
64
|
+
inputSchemaZod: buildRemodelSchema,
|
|
65
|
+
execute: async (args) => {
|
|
66
|
+
if (!client.hasApiKey())
|
|
67
|
+
return client.noApiKeyMessage();
|
|
68
|
+
const data = await client.post("/estimator/estimates/build-remodel", args);
|
|
69
|
+
return JSON.stringify(data);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -32,18 +32,155 @@ declare const uploadTakeoffInputSchema: z.ZodObject<{
|
|
|
32
32
|
fileName: z.ZodString;
|
|
33
33
|
base64: z.ZodString;
|
|
34
34
|
mimeType: z.ZodOptional<z.ZodString>;
|
|
35
|
+
remodelHints: z.ZodOptional<z.ZodObject<{
|
|
36
|
+
cabinetLinearFt: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
rooms: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
38
|
+
name: z.ZodString;
|
|
39
|
+
lengthFt: z.ZodOptional<z.ZodNumber>;
|
|
40
|
+
widthFt: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
}, "strip", z.ZodTypeAny, {
|
|
42
|
+
name: string;
|
|
43
|
+
lengthFt?: number | undefined;
|
|
44
|
+
widthFt?: number | undefined;
|
|
45
|
+
}, {
|
|
46
|
+
name: string;
|
|
47
|
+
lengthFt?: number | undefined;
|
|
48
|
+
widthFt?: number | undefined;
|
|
49
|
+
}>, "many">>;
|
|
50
|
+
baths: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
51
|
+
type: z.ZodOptional<z.ZodEnum<["full", "half", "three_quarter", "unknown"]>>;
|
|
52
|
+
count: z.ZodOptional<z.ZodNumber>;
|
|
53
|
+
label: z.ZodOptional<z.ZodString>;
|
|
54
|
+
}, "strip", z.ZodTypeAny, {
|
|
55
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
56
|
+
count?: number | undefined;
|
|
57
|
+
label?: string | undefined;
|
|
58
|
+
}, {
|
|
59
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
60
|
+
count?: number | undefined;
|
|
61
|
+
label?: string | undefined;
|
|
62
|
+
}>, "many">>;
|
|
63
|
+
flooringSqFt: z.ZodOptional<z.ZodNumber>;
|
|
64
|
+
drywallSqFt: z.ZodOptional<z.ZodNumber>;
|
|
65
|
+
paintSqFt: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
roofSqFt: z.ZodOptional<z.ZodNumber>;
|
|
67
|
+
flags: z.ZodOptional<z.ZodObject<{
|
|
68
|
+
wallMovingRisk: z.ZodOptional<z.ZodBoolean>;
|
|
69
|
+
electricalSurpriseRisk: z.ZodOptional<z.ZodBoolean>;
|
|
70
|
+
plumbingSurpriseRisk: z.ZodOptional<z.ZodBoolean>;
|
|
71
|
+
}, "strip", z.ZodTypeAny, {
|
|
72
|
+
wallMovingRisk?: boolean | undefined;
|
|
73
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
74
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
75
|
+
}, {
|
|
76
|
+
wallMovingRisk?: boolean | undefined;
|
|
77
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
78
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
79
|
+
}>>;
|
|
80
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
81
|
+
}, "strip", z.ZodTypeAny, {
|
|
82
|
+
notes?: string | undefined;
|
|
83
|
+
cabinetLinearFt?: number | undefined;
|
|
84
|
+
rooms?: {
|
|
85
|
+
name: string;
|
|
86
|
+
lengthFt?: number | undefined;
|
|
87
|
+
widthFt?: number | undefined;
|
|
88
|
+
}[] | undefined;
|
|
89
|
+
baths?: {
|
|
90
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
91
|
+
count?: number | undefined;
|
|
92
|
+
label?: string | undefined;
|
|
93
|
+
}[] | undefined;
|
|
94
|
+
flooringSqFt?: number | undefined;
|
|
95
|
+
drywallSqFt?: number | undefined;
|
|
96
|
+
paintSqFt?: number | undefined;
|
|
97
|
+
roofSqFt?: number | undefined;
|
|
98
|
+
flags?: {
|
|
99
|
+
wallMovingRisk?: boolean | undefined;
|
|
100
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
101
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
102
|
+
} | undefined;
|
|
103
|
+
}, {
|
|
104
|
+
notes?: string | undefined;
|
|
105
|
+
cabinetLinearFt?: number | undefined;
|
|
106
|
+
rooms?: {
|
|
107
|
+
name: string;
|
|
108
|
+
lengthFt?: number | undefined;
|
|
109
|
+
widthFt?: number | undefined;
|
|
110
|
+
}[] | undefined;
|
|
111
|
+
baths?: {
|
|
112
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
113
|
+
count?: number | undefined;
|
|
114
|
+
label?: string | undefined;
|
|
115
|
+
}[] | undefined;
|
|
116
|
+
flooringSqFt?: number | undefined;
|
|
117
|
+
drywallSqFt?: number | undefined;
|
|
118
|
+
paintSqFt?: number | undefined;
|
|
119
|
+
roofSqFt?: number | undefined;
|
|
120
|
+
flags?: {
|
|
121
|
+
wallMovingRisk?: boolean | undefined;
|
|
122
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
123
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
124
|
+
} | undefined;
|
|
125
|
+
}>>;
|
|
35
126
|
}, "strip", z.ZodTypeAny, {
|
|
36
127
|
base64: string;
|
|
37
128
|
projectId: string;
|
|
38
129
|
kind: string;
|
|
39
130
|
fileName: string;
|
|
40
131
|
mimeType?: string | undefined;
|
|
132
|
+
remodelHints?: {
|
|
133
|
+
notes?: string | undefined;
|
|
134
|
+
cabinetLinearFt?: number | undefined;
|
|
135
|
+
rooms?: {
|
|
136
|
+
name: string;
|
|
137
|
+
lengthFt?: number | undefined;
|
|
138
|
+
widthFt?: number | undefined;
|
|
139
|
+
}[] | undefined;
|
|
140
|
+
baths?: {
|
|
141
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
142
|
+
count?: number | undefined;
|
|
143
|
+
label?: string | undefined;
|
|
144
|
+
}[] | undefined;
|
|
145
|
+
flooringSqFt?: number | undefined;
|
|
146
|
+
drywallSqFt?: number | undefined;
|
|
147
|
+
paintSqFt?: number | undefined;
|
|
148
|
+
roofSqFt?: number | undefined;
|
|
149
|
+
flags?: {
|
|
150
|
+
wallMovingRisk?: boolean | undefined;
|
|
151
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
152
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
153
|
+
} | undefined;
|
|
154
|
+
} | undefined;
|
|
41
155
|
}, {
|
|
42
156
|
base64: string;
|
|
43
157
|
projectId: string;
|
|
44
158
|
kind: string;
|
|
45
159
|
fileName: string;
|
|
46
160
|
mimeType?: string | undefined;
|
|
161
|
+
remodelHints?: {
|
|
162
|
+
notes?: string | undefined;
|
|
163
|
+
cabinetLinearFt?: number | undefined;
|
|
164
|
+
rooms?: {
|
|
165
|
+
name: string;
|
|
166
|
+
lengthFt?: number | undefined;
|
|
167
|
+
widthFt?: number | undefined;
|
|
168
|
+
}[] | undefined;
|
|
169
|
+
baths?: {
|
|
170
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
171
|
+
count?: number | undefined;
|
|
172
|
+
label?: string | undefined;
|
|
173
|
+
}[] | undefined;
|
|
174
|
+
flooringSqFt?: number | undefined;
|
|
175
|
+
drywallSqFt?: number | undefined;
|
|
176
|
+
paintSqFt?: number | undefined;
|
|
177
|
+
roofSqFt?: number | undefined;
|
|
178
|
+
flags?: {
|
|
179
|
+
wallMovingRisk?: boolean | undefined;
|
|
180
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
181
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
182
|
+
} | undefined;
|
|
183
|
+
} | undefined;
|
|
47
184
|
}>;
|
|
48
185
|
declare const listTakeoffRowsSchema: z.ZodObject<{
|
|
49
186
|
projectId: z.ZodString;
|
|
@@ -230,18 +367,155 @@ export declare const uploadTakeoffInput: {
|
|
|
230
367
|
fileName: z.ZodString;
|
|
231
368
|
base64: z.ZodString;
|
|
232
369
|
mimeType: z.ZodOptional<z.ZodString>;
|
|
370
|
+
remodelHints: z.ZodOptional<z.ZodObject<{
|
|
371
|
+
cabinetLinearFt: z.ZodOptional<z.ZodNumber>;
|
|
372
|
+
rooms: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
373
|
+
name: z.ZodString;
|
|
374
|
+
lengthFt: z.ZodOptional<z.ZodNumber>;
|
|
375
|
+
widthFt: z.ZodOptional<z.ZodNumber>;
|
|
376
|
+
}, "strip", z.ZodTypeAny, {
|
|
377
|
+
name: string;
|
|
378
|
+
lengthFt?: number | undefined;
|
|
379
|
+
widthFt?: number | undefined;
|
|
380
|
+
}, {
|
|
381
|
+
name: string;
|
|
382
|
+
lengthFt?: number | undefined;
|
|
383
|
+
widthFt?: number | undefined;
|
|
384
|
+
}>, "many">>;
|
|
385
|
+
baths: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
386
|
+
type: z.ZodOptional<z.ZodEnum<["full", "half", "three_quarter", "unknown"]>>;
|
|
387
|
+
count: z.ZodOptional<z.ZodNumber>;
|
|
388
|
+
label: z.ZodOptional<z.ZodString>;
|
|
389
|
+
}, "strip", z.ZodTypeAny, {
|
|
390
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
391
|
+
count?: number | undefined;
|
|
392
|
+
label?: string | undefined;
|
|
393
|
+
}, {
|
|
394
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
395
|
+
count?: number | undefined;
|
|
396
|
+
label?: string | undefined;
|
|
397
|
+
}>, "many">>;
|
|
398
|
+
flooringSqFt: z.ZodOptional<z.ZodNumber>;
|
|
399
|
+
drywallSqFt: z.ZodOptional<z.ZodNumber>;
|
|
400
|
+
paintSqFt: z.ZodOptional<z.ZodNumber>;
|
|
401
|
+
roofSqFt: z.ZodOptional<z.ZodNumber>;
|
|
402
|
+
flags: z.ZodOptional<z.ZodObject<{
|
|
403
|
+
wallMovingRisk: z.ZodOptional<z.ZodBoolean>;
|
|
404
|
+
electricalSurpriseRisk: z.ZodOptional<z.ZodBoolean>;
|
|
405
|
+
plumbingSurpriseRisk: z.ZodOptional<z.ZodBoolean>;
|
|
406
|
+
}, "strip", z.ZodTypeAny, {
|
|
407
|
+
wallMovingRisk?: boolean | undefined;
|
|
408
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
409
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
410
|
+
}, {
|
|
411
|
+
wallMovingRisk?: boolean | undefined;
|
|
412
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
413
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
414
|
+
}>>;
|
|
415
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
416
|
+
}, "strip", z.ZodTypeAny, {
|
|
417
|
+
notes?: string | undefined;
|
|
418
|
+
cabinetLinearFt?: number | undefined;
|
|
419
|
+
rooms?: {
|
|
420
|
+
name: string;
|
|
421
|
+
lengthFt?: number | undefined;
|
|
422
|
+
widthFt?: number | undefined;
|
|
423
|
+
}[] | undefined;
|
|
424
|
+
baths?: {
|
|
425
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
426
|
+
count?: number | undefined;
|
|
427
|
+
label?: string | undefined;
|
|
428
|
+
}[] | undefined;
|
|
429
|
+
flooringSqFt?: number | undefined;
|
|
430
|
+
drywallSqFt?: number | undefined;
|
|
431
|
+
paintSqFt?: number | undefined;
|
|
432
|
+
roofSqFt?: number | undefined;
|
|
433
|
+
flags?: {
|
|
434
|
+
wallMovingRisk?: boolean | undefined;
|
|
435
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
436
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
437
|
+
} | undefined;
|
|
438
|
+
}, {
|
|
439
|
+
notes?: string | undefined;
|
|
440
|
+
cabinetLinearFt?: number | undefined;
|
|
441
|
+
rooms?: {
|
|
442
|
+
name: string;
|
|
443
|
+
lengthFt?: number | undefined;
|
|
444
|
+
widthFt?: number | undefined;
|
|
445
|
+
}[] | undefined;
|
|
446
|
+
baths?: {
|
|
447
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
448
|
+
count?: number | undefined;
|
|
449
|
+
label?: string | undefined;
|
|
450
|
+
}[] | undefined;
|
|
451
|
+
flooringSqFt?: number | undefined;
|
|
452
|
+
drywallSqFt?: number | undefined;
|
|
453
|
+
paintSqFt?: number | undefined;
|
|
454
|
+
roofSqFt?: number | undefined;
|
|
455
|
+
flags?: {
|
|
456
|
+
wallMovingRisk?: boolean | undefined;
|
|
457
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
458
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
459
|
+
} | undefined;
|
|
460
|
+
}>>;
|
|
233
461
|
}, "strip", z.ZodTypeAny, {
|
|
234
462
|
base64: string;
|
|
235
463
|
projectId: string;
|
|
236
464
|
kind: string;
|
|
237
465
|
fileName: string;
|
|
238
466
|
mimeType?: string | undefined;
|
|
467
|
+
remodelHints?: {
|
|
468
|
+
notes?: string | undefined;
|
|
469
|
+
cabinetLinearFt?: number | undefined;
|
|
470
|
+
rooms?: {
|
|
471
|
+
name: string;
|
|
472
|
+
lengthFt?: number | undefined;
|
|
473
|
+
widthFt?: number | undefined;
|
|
474
|
+
}[] | undefined;
|
|
475
|
+
baths?: {
|
|
476
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
477
|
+
count?: number | undefined;
|
|
478
|
+
label?: string | undefined;
|
|
479
|
+
}[] | undefined;
|
|
480
|
+
flooringSqFt?: number | undefined;
|
|
481
|
+
drywallSqFt?: number | undefined;
|
|
482
|
+
paintSqFt?: number | undefined;
|
|
483
|
+
roofSqFt?: number | undefined;
|
|
484
|
+
flags?: {
|
|
485
|
+
wallMovingRisk?: boolean | undefined;
|
|
486
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
487
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
488
|
+
} | undefined;
|
|
489
|
+
} | undefined;
|
|
239
490
|
}, {
|
|
240
491
|
base64: string;
|
|
241
492
|
projectId: string;
|
|
242
493
|
kind: string;
|
|
243
494
|
fileName: string;
|
|
244
495
|
mimeType?: string | undefined;
|
|
496
|
+
remodelHints?: {
|
|
497
|
+
notes?: string | undefined;
|
|
498
|
+
cabinetLinearFt?: number | undefined;
|
|
499
|
+
rooms?: {
|
|
500
|
+
name: string;
|
|
501
|
+
lengthFt?: number | undefined;
|
|
502
|
+
widthFt?: number | undefined;
|
|
503
|
+
}[] | undefined;
|
|
504
|
+
baths?: {
|
|
505
|
+
type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
|
|
506
|
+
count?: number | undefined;
|
|
507
|
+
label?: string | undefined;
|
|
508
|
+
}[] | undefined;
|
|
509
|
+
flooringSqFt?: number | undefined;
|
|
510
|
+
drywallSqFt?: number | undefined;
|
|
511
|
+
paintSqFt?: number | undefined;
|
|
512
|
+
roofSqFt?: number | undefined;
|
|
513
|
+
flags?: {
|
|
514
|
+
wallMovingRisk?: boolean | undefined;
|
|
515
|
+
electricalSurpriseRisk?: boolean | undefined;
|
|
516
|
+
plumbingSurpriseRisk?: boolean | undefined;
|
|
517
|
+
} | undefined;
|
|
518
|
+
} | undefined;
|
|
245
519
|
}>;
|
|
246
520
|
execute: (args: z.infer<typeof uploadTakeoffInputSchema>) => Promise<string>;
|
|
247
521
|
};
|
|
@@ -9,12 +9,44 @@ const createTakeoffProjectSchema = z.object({
|
|
|
9
9
|
companyId: z.string().optional(),
|
|
10
10
|
});
|
|
11
11
|
const listTakeoffProjectsSchema = z.object({});
|
|
12
|
+
const remodelHintsSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
cabinetLinearFt: z.number().nonnegative().optional(),
|
|
15
|
+
rooms: z
|
|
16
|
+
.array(z.object({
|
|
17
|
+
name: z.string(),
|
|
18
|
+
lengthFt: z.number().positive().optional(),
|
|
19
|
+
widthFt: z.number().positive().optional(),
|
|
20
|
+
}))
|
|
21
|
+
.optional(),
|
|
22
|
+
baths: z
|
|
23
|
+
.array(z.object({
|
|
24
|
+
type: z.enum(["full", "half", "three_quarter", "unknown"]).optional(),
|
|
25
|
+
count: z.number().int().positive().optional(),
|
|
26
|
+
label: z.string().optional(),
|
|
27
|
+
}))
|
|
28
|
+
.optional(),
|
|
29
|
+
flooringSqFt: z.number().nonnegative().optional(),
|
|
30
|
+
drywallSqFt: z.number().nonnegative().optional(),
|
|
31
|
+
paintSqFt: z.number().nonnegative().optional(),
|
|
32
|
+
roofSqFt: z.number().nonnegative().optional(),
|
|
33
|
+
flags: z
|
|
34
|
+
.object({
|
|
35
|
+
wallMovingRisk: z.boolean().optional(),
|
|
36
|
+
electricalSurpriseRisk: z.boolean().optional(),
|
|
37
|
+
plumbingSurpriseRisk: z.boolean().optional(),
|
|
38
|
+
})
|
|
39
|
+
.optional(),
|
|
40
|
+
notes: z.string().optional(),
|
|
41
|
+
})
|
|
42
|
+
.optional();
|
|
12
43
|
const uploadTakeoffInputSchema = z.object({
|
|
13
44
|
projectId: z.string().uuid(),
|
|
14
45
|
kind: z.string(),
|
|
15
46
|
fileName: z.string(),
|
|
16
47
|
base64: z.string(),
|
|
17
48
|
mimeType: z.string().optional(),
|
|
49
|
+
remodelHints: remodelHintsSchema,
|
|
18
50
|
});
|
|
19
51
|
const listTakeoffRowsSchema = z.object({
|
|
20
52
|
projectId: z.string().uuid(),
|
|
@@ -102,17 +134,19 @@ export const listTakeoffProjects = {
|
|
|
102
134
|
};
|
|
103
135
|
export const uploadTakeoffInput = {
|
|
104
136
|
name: "estimator_upload_takeoff_input",
|
|
105
|
-
description: "
|
|
137
|
+
description: "Upload a takeoff input file (base64) for a project. kind: eagleview_pdf|eagleview_xml|eagleview_json|eagleview_csv|spec_pdf|spreadsheet|photo|voice|other. For remodel photo/drawing takeoff, use kind=photo or spec_pdf and pass optional remodelHints (cabinet LF, room dims, bath counts, flooring/drywall/paint/roof sf, risk flags) — quantities are reviewable, not auto-measured from pixels.",
|
|
106
138
|
inputSchemaZod: uploadTakeoffInputSchema,
|
|
107
139
|
execute: async (args) => {
|
|
108
140
|
if (!client.hasApiKey())
|
|
109
141
|
return client.noApiKeyMessage();
|
|
110
|
-
|
|
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,
|
|
142
|
+
const data = await client.post(`/estimator/takeoffs/${args.projectId}/inputs`, {
|
|
113
143
|
kind: args.kind,
|
|
114
144
|
fileName: args.fileName,
|
|
145
|
+
base64: args.base64,
|
|
146
|
+
...(args.mimeType ? { mimeType: args.mimeType } : {}),
|
|
147
|
+
...(args.remodelHints ? { remodelHints: args.remodelHints } : {}),
|
|
115
148
|
});
|
|
149
|
+
return JSON.stringify(data);
|
|
116
150
|
},
|
|
117
151
|
};
|
|
118
152
|
export const runTakeoffExtract = {
|
|
@@ -15,7 +15,7 @@ export const analyzePhotos = {
|
|
|
15
15
|
const data = await client.post("/estimator/analyze", {
|
|
16
16
|
type: "photos",
|
|
17
17
|
images: args.images,
|
|
18
|
-
prompt: args.prompt,
|
|
18
|
+
...(args.prompt ? { prompt: args.prompt } : {}),
|
|
19
19
|
});
|
|
20
20
|
return JSON.stringify(data);
|
|
21
21
|
},
|
|
@@ -35,7 +35,7 @@ export const analyzeDocument = {
|
|
|
35
35
|
const data = await client.post("/estimator/analyze", {
|
|
36
36
|
type: "document",
|
|
37
37
|
text: args.text,
|
|
38
|
-
prompt: args.prompt,
|
|
38
|
+
...(args.prompt ? { prompt: args.prompt } : {}),
|
|
39
39
|
});
|
|
40
40
|
return JSON.stringify(data);
|
|
41
41
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kynver-app/mcp-estimator",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist",
|
|
6
|
+
"!dist/**/__tests__/**",
|
|
7
|
+
"!dist/**/*.map"
|
|
8
|
+
],
|
|
4
9
|
"description": "Kynver Estimator MCP server — chat, estimate, and catalog tools",
|
|
5
10
|
"type": "module",
|
|
6
11
|
"bin": {
|
package/src/index.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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);
|
package/src/lib/kynver-client.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thin fetch wrapper for Kynver API. Base URL from KYNVER_API_URL; optional Bearer from KYNVER_API_KEY.
|
|
3
|
-
*
|
|
4
|
-
* Supports per-request user identity via AsyncLocalStorage. When a userId is
|
|
5
|
-
* set in the request context (e.g. from the SSE route), all API calls include
|
|
6
|
-
* X-Kynver-User-Id and X-Kynver-Service-Secret headers so the Kynver API
|
|
7
|
-
* executes operations on behalf of the correct user.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Request context — allows the SSE route to set userId per-connection
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
interface RequestContext {
|
|
17
|
-
userId?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const requestContext = new AsyncLocalStorage<RequestContext>();
|
|
21
|
-
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Config
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
const baseUrl = process.env.KYNVER_API_URL ?? "https://kynver.com";
|
|
27
|
-
const apiKey = process.env.KYNVER_API_KEY ?? "";
|
|
28
|
-
|
|
29
|
-
function assertSafeApiPath(path: string): void {
|
|
30
|
-
const p = path.startsWith("/") ? path : `/${path}`;
|
|
31
|
-
if (p.includes("..") || p.includes("\0")) {
|
|
32
|
-
throw new Error("Invalid API path: path traversal is not allowed");
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function url(path: string): string {
|
|
37
|
-
assertSafeApiPath(path);
|
|
38
|
-
const p = path.startsWith("/") ? path : `/${path}`;
|
|
39
|
-
const apiPath = p.startsWith("/api") ? p : `/api${p}`;
|
|
40
|
-
return `${baseUrl.replace(/\/$/, "")}${apiPath}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function getHeaders(): Record<string, string> {
|
|
44
|
-
const headers: Record<string, string> = {
|
|
45
|
-
"Content-Type": "application/json",
|
|
46
|
-
};
|
|
47
|
-
if (apiKey) {
|
|
48
|
-
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const ctx = requestContext.getStore();
|
|
52
|
-
const userId = ctx?.userId ?? process.env.KYNVER_USER_ID?.trim();
|
|
53
|
-
const serviceSecret = process.env.KYNVER_SERVICE_SECRET?.trim();
|
|
54
|
-
|
|
55
|
-
if (userId) {
|
|
56
|
-
if (!serviceSecret) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
"X-Kynver-User-Id was set in context but KYNVER_SERVICE_SECRET is missing; refusing to send an unscoped request.",
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
headers["X-Kynver-User-Id"] = userId;
|
|
62
|
-
headers["X-Kynver-Service-Secret"] = serviceSecret;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return headers;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
// Response handling
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
async function handleResponse<T>(res: Response): Promise<T> {
|
|
73
|
-
const text = await res.text();
|
|
74
|
-
if (!res.ok) {
|
|
75
|
-
let msg = text;
|
|
76
|
-
try {
|
|
77
|
-
const j = JSON.parse(text) as { error?: string; message?: string };
|
|
78
|
-
msg = j.error ?? j.message ?? text;
|
|
79
|
-
} catch {
|
|
80
|
-
// use text as-is
|
|
81
|
-
}
|
|
82
|
-
throw new Error(msg || `HTTP ${res.status}`);
|
|
83
|
-
}
|
|
84
|
-
if (!text) return undefined as T;
|
|
85
|
-
try {
|
|
86
|
-
return JSON.parse(text) as T;
|
|
87
|
-
} catch {
|
|
88
|
-
return text as unknown as T;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
// HTTP methods
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
|
|
96
|
-
export async function get<T = unknown>(path: string): Promise<T> {
|
|
97
|
-
const res = await fetch(url(path), {
|
|
98
|
-
method: "GET",
|
|
99
|
-
headers: getHeaders(),
|
|
100
|
-
});
|
|
101
|
-
return handleResponse<T>(res);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export async function post<T = unknown>(path: string, body: unknown): Promise<T> {
|
|
105
|
-
const res = await fetch(url(path), {
|
|
106
|
-
method: "POST",
|
|
107
|
-
headers: getHeaders(),
|
|
108
|
-
body: JSON.stringify(body),
|
|
109
|
-
});
|
|
110
|
-
return handleResponse<T>(res);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export async function patch<T = unknown>(path: string, body: unknown): Promise<T> {
|
|
114
|
-
const res = await fetch(url(path), {
|
|
115
|
-
method: "PATCH",
|
|
116
|
-
headers: getHeaders(),
|
|
117
|
-
body: JSON.stringify(body),
|
|
118
|
-
});
|
|
119
|
-
return handleResponse<T>(res);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export async function del<T = unknown>(path: string): Promise<T> {
|
|
123
|
-
const res = await fetch(url(path), {
|
|
124
|
-
method: "DELETE",
|
|
125
|
-
headers: getHeaders(),
|
|
126
|
-
});
|
|
127
|
-
return handleResponse<T>(res);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function hasApiKey(): boolean {
|
|
131
|
-
return !!apiKey;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function noApiKeyMessage(): string {
|
|
135
|
-
return "No API key configured. Set KYNVER_API_KEY in your MCP server config.";
|
|
136
|
-
}
|