@kynver-app/mcp-estimator 0.1.1 → 0.2.0
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/package.json +1 -1
- package/src/server.ts +120 -101
- package/src/tools/takeoff-tools.ts +252 -0
package/package.json
CHANGED
package/src/server.ts
CHANGED
|
@@ -1,101 +1,120 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates and configures the estimator MCP server with all tools registered.
|
|
3
|
-
* Separated from transport so it can be used with stdio (CLI) or SSE (hosted).
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
-
import * as catalogTools from "./tools/catalog-tools.js";
|
|
8
|
-
import * as estimateTools from "./tools/estimate-tools.js";
|
|
9
|
-
import * as templateTools from "./tools/template-tools.js";
|
|
10
|
-
import * as versionTools from "./tools/version-tools.js";
|
|
11
|
-
import * as photoTools from "./tools/photo-tools.js";
|
|
12
|
-
import * as statsPdfTools from "./tools/stats-pdf-tools.js";
|
|
13
|
-
import * as visionTools from "./tools/vision-tools.js";
|
|
14
|
-
import * as companyTools from "./tools/company-tools.js";
|
|
15
|
-
import * as clientTools from "./tools/client-tools.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
register(catalogTools.
|
|
41
|
-
register(catalogTools.
|
|
42
|
-
register(catalogTools.
|
|
43
|
-
register(catalogTools.
|
|
44
|
-
register(catalogTools.
|
|
45
|
-
register(catalogTools.
|
|
46
|
-
register(catalogTools.
|
|
47
|
-
register(catalogTools.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
register(estimateTools.
|
|
52
|
-
register(estimateTools.
|
|
53
|
-
register(estimateTools.
|
|
54
|
-
register(estimateTools.
|
|
55
|
-
register(estimateTools.
|
|
56
|
-
register(estimateTools.
|
|
57
|
-
register(estimateTools.
|
|
58
|
-
register(estimateTools.
|
|
59
|
-
register(estimateTools.
|
|
60
|
-
register(estimateTools.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
register(templateTools.
|
|
65
|
-
register(templateTools.
|
|
66
|
-
register(templateTools.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
register(versionTools.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
register(photoTools.
|
|
75
|
-
register(photoTools.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
register(statsPdfTools.
|
|
80
|
-
register(statsPdfTools.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
register(visionTools.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
register(companyTools.
|
|
89
|
-
register(companyTools.
|
|
90
|
-
register(companyTools.
|
|
91
|
-
register(companyTools.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
register(clientTools.
|
|
96
|
-
register(clientTools.
|
|
97
|
-
register(clientTools.
|
|
98
|
-
register(clientTools.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Creates and configures the estimator MCP server with all tools registered.
|
|
3
|
+
* Separated from transport so it can be used with stdio (CLI) or SSE (hosted).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import * as catalogTools from "./tools/catalog-tools.js";
|
|
8
|
+
import * as estimateTools from "./tools/estimate-tools.js";
|
|
9
|
+
import * as templateTools from "./tools/template-tools.js";
|
|
10
|
+
import * as versionTools from "./tools/version-tools.js";
|
|
11
|
+
import * as photoTools from "./tools/photo-tools.js";
|
|
12
|
+
import * as statsPdfTools from "./tools/stats-pdf-tools.js";
|
|
13
|
+
import * as visionTools from "./tools/vision-tools.js";
|
|
14
|
+
import * as companyTools from "./tools/company-tools.js";
|
|
15
|
+
import * as clientTools from "./tools/client-tools.js";
|
|
16
|
+
import * as takeoffTools from "./tools/takeoff-tools.js";
|
|
17
|
+
|
|
18
|
+
type ToolConfig = { description: string; inputSchema: unknown };
|
|
19
|
+
|
|
20
|
+
function toolResult(text: string) {
|
|
21
|
+
return { content: [{ type: "text" as const, text }] };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createEstimatorServer(): McpServer {
|
|
25
|
+
const server = new McpServer(
|
|
26
|
+
{ name: "kynver-mcp-estimator", version: "0.2.0" },
|
|
27
|
+
{ capabilities: { tools: {} } }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
function register(
|
|
31
|
+
name: string,
|
|
32
|
+
config: ToolConfig,
|
|
33
|
+
run: (args: unknown) => Promise<string>
|
|
34
|
+
) {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
server.registerTool(name, config as any, async (args: any) => toolResult(await run(args)));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- Catalog tools (9) ---
|
|
40
|
+
register(catalogTools.importCatalog.name, { description: catalogTools.importCatalog.description, inputSchema: catalogTools.importCatalog.inputSchemaZod }, (args) => catalogTools.importCatalog.execute(args as any));
|
|
41
|
+
register(catalogTools.reviewImport.name, { description: catalogTools.reviewImport.description, inputSchema: catalogTools.reviewImport.inputSchemaZod }, (args) => catalogTools.reviewImport.execute(args as any));
|
|
42
|
+
register(catalogTools.commitImport.name, { description: catalogTools.commitImport.description, inputSchema: catalogTools.commitImport.inputSchemaZod }, (args) => catalogTools.commitImport.execute(args as any));
|
|
43
|
+
register(catalogTools.setupPrice.name, { description: catalogTools.setupPrice.description, inputSchema: catalogTools.setupPrice.inputSchemaZod }, (args) => catalogTools.setupPrice.execute(args as any));
|
|
44
|
+
register(catalogTools.updatePrice.name, { description: catalogTools.updatePrice.description, inputSchema: catalogTools.updatePrice.inputSchemaZod }, (args) => catalogTools.updatePrice.execute(args as any));
|
|
45
|
+
register(catalogTools.getCatalog.name, { description: catalogTools.getCatalog.description, inputSchema: catalogTools.getCatalog.inputSchemaZod }, (args) => catalogTools.getCatalog.execute(args as any));
|
|
46
|
+
register(catalogTools.getConfig.name, { description: catalogTools.getConfig.description, inputSchema: catalogTools.getConfig.inputSchemaZod }, (args) => catalogTools.getConfig.execute(args as any));
|
|
47
|
+
register(catalogTools.updateConfig.name, { description: catalogTools.updateConfig.description, inputSchema: catalogTools.updateConfig.inputSchemaZod }, (args) => catalogTools.updateConfig.execute(args as any));
|
|
48
|
+
register(catalogTools.importTemplate.name, { description: catalogTools.importTemplate.description, inputSchema: catalogTools.importTemplate.inputSchemaZod }, (args) => catalogTools.importTemplate.execute(args as any));
|
|
49
|
+
|
|
50
|
+
// --- Estimate tools (11) ---
|
|
51
|
+
register(estimateTools.createReplacement.name, { description: estimateTools.createReplacement.description, inputSchema: estimateTools.createReplacement.inputSchemaZod }, (args) => estimateTools.createReplacement.execute(args as any));
|
|
52
|
+
register(estimateTools.createRepair.name, { description: estimateTools.createRepair.description, inputSchema: estimateTools.createRepair.inputSchemaZod }, (args) => estimateTools.createRepair.execute(args as any));
|
|
53
|
+
register(estimateTools.getEstimate.name, { description: estimateTools.getEstimate.description, inputSchema: estimateTools.getEstimate.inputSchemaZod }, (args) => estimateTools.getEstimate.execute(args as any));
|
|
54
|
+
register(estimateTools.listEstimates.name, { description: estimateTools.listEstimates.description, inputSchema: estimateTools.listEstimates.inputSchemaZod }, (args) => estimateTools.listEstimates.execute(args as any));
|
|
55
|
+
register(estimateTools.addLineItem.name, { description: estimateTools.addLineItem.description, inputSchema: estimateTools.addLineItem.inputSchemaZod }, (args) => estimateTools.addLineItem.execute(args as any));
|
|
56
|
+
register(estimateTools.removeLineItem.name, { description: estimateTools.removeLineItem.description, inputSchema: estimateTools.removeLineItem.inputSchemaZod }, (args) => estimateTools.removeLineItem.execute(args as any));
|
|
57
|
+
register(estimateTools.updateLineItem.name, { description: estimateTools.updateLineItem.description, inputSchema: estimateTools.updateLineItem.inputSchemaZod }, (args) => estimateTools.updateLineItem.execute(args as any));
|
|
58
|
+
register(estimateTools.adjustToTarget.name, { description: estimateTools.adjustToTarget.description, inputSchema: estimateTools.adjustToTarget.inputSchemaZod }, (args) => estimateTools.adjustToTarget.execute(args as any));
|
|
59
|
+
register(estimateTools.updateEstimate.name, { description: estimateTools.updateEstimate.description, inputSchema: estimateTools.updateEstimate.inputSchemaZod }, (args) => estimateTools.updateEstimate.execute(args as any));
|
|
60
|
+
register(estimateTools.deleteEstimate.name, { description: estimateTools.deleteEstimate.description, inputSchema: estimateTools.deleteEstimate.inputSchemaZod }, (args) => estimateTools.deleteEstimate.execute(args as any));
|
|
61
|
+
register(estimateTools.duplicateEstimate.name, { description: estimateTools.duplicateEstimate.description, inputSchema: estimateTools.duplicateEstimate.inputSchemaZod }, (args) => estimateTools.duplicateEstimate.execute(args as any));
|
|
62
|
+
|
|
63
|
+
// --- Template tools (4) ---
|
|
64
|
+
register(templateTools.saveTemplate.name, { description: templateTools.saveTemplate.description, inputSchema: templateTools.saveTemplate.inputSchemaZod }, (args) => templateTools.saveTemplate.execute(args as any));
|
|
65
|
+
register(templateTools.listTemplates.name, { description: templateTools.listTemplates.description, inputSchema: templateTools.listTemplates.inputSchemaZod }, (args) => templateTools.listTemplates.execute(args as any));
|
|
66
|
+
register(templateTools.deleteTemplate.name, { description: templateTools.deleteTemplate.description, inputSchema: templateTools.deleteTemplate.inputSchemaZod }, (args) => templateTools.deleteTemplate.execute(args as any));
|
|
67
|
+
register(templateTools.createFromTemplate.name, { description: templateTools.createFromTemplate.description, inputSchema: templateTools.createFromTemplate.inputSchemaZod }, (args) => templateTools.createFromTemplate.execute(args as any));
|
|
68
|
+
|
|
69
|
+
// --- Version tools (2) ---
|
|
70
|
+
register(versionTools.listVersions.name, { description: versionTools.listVersions.description, inputSchema: versionTools.listVersions.inputSchemaZod }, (args) => versionTools.listVersions.execute(args as any));
|
|
71
|
+
register(versionTools.getVersion.name, { description: versionTools.getVersion.description, inputSchema: versionTools.getVersion.inputSchemaZod }, (args) => versionTools.getVersion.execute(args as any));
|
|
72
|
+
|
|
73
|
+
// --- Photo tools (3) ---
|
|
74
|
+
register(photoTools.uploadPhoto.name, { description: photoTools.uploadPhoto.description, inputSchema: photoTools.uploadPhoto.inputSchemaZod }, (args) => photoTools.uploadPhoto.execute(args as any));
|
|
75
|
+
register(photoTools.listPhotos.name, { description: photoTools.listPhotos.description, inputSchema: photoTools.listPhotos.inputSchemaZod }, (args) => photoTools.listPhotos.execute(args as any));
|
|
76
|
+
register(photoTools.deletePhoto.name, { description: photoTools.deletePhoto.description, inputSchema: photoTools.deletePhoto.inputSchemaZod }, (args) => photoTools.deletePhoto.execute(args as any));
|
|
77
|
+
|
|
78
|
+
// --- Stats / PDF tools (3) ---
|
|
79
|
+
register(statsPdfTools.getStats.name, { description: statsPdfTools.getStats.description, inputSchema: statsPdfTools.getStats.inputSchemaZod }, (args) => statsPdfTools.getStats.execute(args as any));
|
|
80
|
+
register(statsPdfTools.exportPdf.name, { description: statsPdfTools.exportPdf.description, inputSchema: statsPdfTools.exportPdf.inputSchemaZod }, (args) => statsPdfTools.exportPdf.execute(args as any));
|
|
81
|
+
register(statsPdfTools.updateOutputConfig.name, { description: statsPdfTools.updateOutputConfig.description, inputSchema: statsPdfTools.updateOutputConfig.inputSchemaZod }, (args) => statsPdfTools.updateOutputConfig.execute(args as any));
|
|
82
|
+
|
|
83
|
+
// --- Vision tools (2) ---
|
|
84
|
+
register(visionTools.analyzePhotos.name, { description: visionTools.analyzePhotos.description, inputSchema: visionTools.analyzePhotos.inputSchemaZod }, (args) => visionTools.analyzePhotos.execute(args as any));
|
|
85
|
+
register(visionTools.analyzeDocument.name, { description: visionTools.analyzeDocument.description, inputSchema: visionTools.analyzeDocument.inputSchemaZod }, (args) => visionTools.analyzeDocument.execute(args as any));
|
|
86
|
+
|
|
87
|
+
// --- Company tools (5) ---
|
|
88
|
+
register(companyTools.createCompany.name, { description: companyTools.createCompany.description, inputSchema: companyTools.createCompany.inputSchemaZod }, (args) => companyTools.createCompany.execute(args as any));
|
|
89
|
+
register(companyTools.listCompanies.name, { description: companyTools.listCompanies.description, inputSchema: companyTools.listCompanies.inputSchemaZod }, (args) => companyTools.listCompanies.execute(args as any));
|
|
90
|
+
register(companyTools.getCompany.name, { description: companyTools.getCompany.description, inputSchema: companyTools.getCompany.inputSchemaZod }, (args) => companyTools.getCompany.execute(args as any));
|
|
91
|
+
register(companyTools.updateCompany.name, { description: companyTools.updateCompany.description, inputSchema: companyTools.updateCompany.inputSchemaZod }, (args) => companyTools.updateCompany.execute(args as any));
|
|
92
|
+
register(companyTools.deleteCompany.name, { description: companyTools.deleteCompany.description, inputSchema: companyTools.deleteCompany.inputSchemaZod }, (args) => companyTools.deleteCompany.execute(args as any));
|
|
93
|
+
|
|
94
|
+
// --- Client tools (5) ---
|
|
95
|
+
register(clientTools.createClient.name, { description: clientTools.createClient.description, inputSchema: clientTools.createClient.inputSchemaZod }, (args) => clientTools.createClient.execute(args as any));
|
|
96
|
+
register(clientTools.listClients.name, { description: clientTools.listClients.description, inputSchema: clientTools.listClients.inputSchemaZod }, (args) => clientTools.listClients.execute(args as any));
|
|
97
|
+
register(clientTools.getClient.name, { description: clientTools.getClient.description, inputSchema: clientTools.getClient.inputSchemaZod }, (args) => clientTools.getClient.execute(args as any));
|
|
98
|
+
register(clientTools.updateClient.name, { description: clientTools.updateClient.description, inputSchema: clientTools.updateClient.inputSchemaZod }, (args) => clientTools.updateClient.execute(args as any));
|
|
99
|
+
register(clientTools.deleteClient.name, { description: clientTools.deleteClient.description, inputSchema: clientTools.deleteClient.inputSchemaZod }, (args) => clientTools.deleteClient.execute(args as any));
|
|
100
|
+
|
|
101
|
+
// --- Takeoff / approval tools (16) ---
|
|
102
|
+
register(takeoffTools.createTakeoffProject.name, { description: takeoffTools.createTakeoffProject.description, inputSchema: takeoffTools.createTakeoffProject.inputSchemaZod }, (args) => takeoffTools.createTakeoffProject.execute(args as any));
|
|
103
|
+
register(takeoffTools.getTakeoffProject.name, { description: takeoffTools.getTakeoffProject.description, inputSchema: takeoffTools.getTakeoffProject.inputSchemaZod }, (args) => takeoffTools.getTakeoffProject.execute(args as any));
|
|
104
|
+
register(takeoffTools.listTakeoffProjects.name, { description: takeoffTools.listTakeoffProjects.description, inputSchema: takeoffTools.listTakeoffProjects.inputSchemaZod }, (args) => takeoffTools.listTakeoffProjects.execute(args as any));
|
|
105
|
+
register(takeoffTools.uploadTakeoffInput.name, { description: takeoffTools.uploadTakeoffInput.description, inputSchema: takeoffTools.uploadTakeoffInput.inputSchemaZod }, (args) => takeoffTools.uploadTakeoffInput.execute(args as any));
|
|
106
|
+
register(takeoffTools.runTakeoffExtract.name, { description: takeoffTools.runTakeoffExtract.description, inputSchema: takeoffTools.runTakeoffExtract.inputSchemaZod }, (args) => takeoffTools.runTakeoffExtract.execute(args as any));
|
|
107
|
+
register(takeoffTools.listTakeoffRows.name, { description: takeoffTools.listTakeoffRows.description, inputSchema: takeoffTools.listTakeoffRows.inputSchemaZod }, (args) => takeoffTools.listTakeoffRows.execute(args as any));
|
|
108
|
+
register(takeoffTools.reviewTakeoffRow.name, { description: takeoffTools.reviewTakeoffRow.description, inputSchema: takeoffTools.reviewTakeoffRow.inputSchemaZod }, (args) => takeoffTools.reviewTakeoffRow.execute(args as any));
|
|
109
|
+
register(takeoffTools.commitTakeoff.name, { description: takeoffTools.commitTakeoff.description, inputSchema: takeoffTools.commitTakeoff.inputSchemaZod }, (args) => takeoffTools.commitTakeoff.execute(args as any));
|
|
110
|
+
register(takeoffTools.listSystemPacks.name, { description: takeoffTools.listSystemPacks.description, inputSchema: takeoffTools.listSystemPacks.inputSchemaZod }, (args) => takeoffTools.listSystemPacks.execute(args as any));
|
|
111
|
+
register(takeoffTools.applySystemPack.name, { description: takeoffTools.applySystemPack.description, inputSchema: takeoffTools.applySystemPack.inputSchemaZod }, (args) => takeoffTools.applySystemPack.execute(args as any));
|
|
112
|
+
register(takeoffTools.listServiceProfiles.name, { description: takeoffTools.listServiceProfiles.description, inputSchema: takeoffTools.listServiceProfiles.inputSchemaZod }, (args) => takeoffTools.listServiceProfiles.execute(args as any));
|
|
113
|
+
register(takeoffTools.upsertServiceProfile.name, { description: takeoffTools.upsertServiceProfile.description, inputSchema: takeoffTools.upsertServiceProfile.inputSchemaZod }, (args) => takeoffTools.upsertServiceProfile.execute(args as any));
|
|
114
|
+
register(takeoffTools.generateEstimateFromTakeoff.name, { description: takeoffTools.generateEstimateFromTakeoff.description, inputSchema: takeoffTools.generateEstimateFromTakeoff.inputSchemaZod }, (args) => takeoffTools.generateEstimateFromTakeoff.execute(args as any));
|
|
115
|
+
register(takeoffTools.requestEstimateApproval.name, { description: takeoffTools.requestEstimateApproval.description, inputSchema: takeoffTools.requestEstimateApproval.inputSchemaZod }, (args) => takeoffTools.requestEstimateApproval.execute(args as any));
|
|
116
|
+
register(takeoffTools.approveEstimate.name, { description: takeoffTools.approveEstimate.description, inputSchema: takeoffTools.approveEstimate.inputSchemaZod }, (args) => takeoffTools.approveEstimate.execute(args as any));
|
|
117
|
+
register(takeoffTools.getEstimateApproval.name, { description: takeoffTools.getEstimateApproval.description, inputSchema: takeoffTools.getEstimateApproval.inputSchemaZod }, (args) => takeoffTools.getEstimateApproval.execute(args as any));
|
|
118
|
+
|
|
119
|
+
return server;
|
|
120
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as client from "../lib/kynver-client.js";
|
|
3
|
+
|
|
4
|
+
const projectIdSchema = z.object({ projectId: z.string().uuid() });
|
|
5
|
+
|
|
6
|
+
export const createTakeoffProject = {
|
|
7
|
+
name: "estimator_create_takeoff_project",
|
|
8
|
+
description:
|
|
9
|
+
"Create a takeoff project shell (commercial or residential). Upload inputs, run extract, review rows, commit, apply system pack, then generate estimate.",
|
|
10
|
+
inputSchemaZod: z.object({
|
|
11
|
+
projectName: z.string(),
|
|
12
|
+
buildingType: z.enum(["commercial", "residential"]),
|
|
13
|
+
siteAddress: z.string().optional(),
|
|
14
|
+
clientId: z.string().optional(),
|
|
15
|
+
companyId: z.string().optional(),
|
|
16
|
+
}),
|
|
17
|
+
execute: async (args: z.infer<typeof createTakeoffProject.inputSchemaZod>): Promise<string> => {
|
|
18
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
19
|
+
const data = await client.post("/estimator/takeoffs", args);
|
|
20
|
+
return JSON.stringify(data);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const getTakeoffProject = {
|
|
25
|
+
name: "estimator_get_takeoff_project",
|
|
26
|
+
description: "Get takeoff project with inputs, rows, status, pack/profile, linked estimate.",
|
|
27
|
+
inputSchemaZod: projectIdSchema,
|
|
28
|
+
execute: async (args: z.infer<typeof projectIdSchema>): Promise<string> => {
|
|
29
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
30
|
+
const data = await client.get(`/estimator/takeoffs/${args.projectId}`);
|
|
31
|
+
return JSON.stringify(data);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const listTakeoffProjects = {
|
|
36
|
+
name: "estimator_list_takeoff_projects",
|
|
37
|
+
description: "List takeoff projects for the workspace.",
|
|
38
|
+
inputSchemaZod: z.object({}),
|
|
39
|
+
execute: async (): Promise<string> => {
|
|
40
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
41
|
+
const data = await client.get("/estimator/takeoffs");
|
|
42
|
+
return JSON.stringify(data);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const uploadTakeoffInput = {
|
|
47
|
+
name: "estimator_upload_takeoff_input",
|
|
48
|
+
description:
|
|
49
|
+
"Register a takeoff file upload. Pass base64 file content via the API route from your client, or use the web UI multipart upload at POST /api/estimator/takeoffs/{id}/inputs with kind + file.",
|
|
50
|
+
inputSchemaZod: z.object({
|
|
51
|
+
projectId: z.string().uuid(),
|
|
52
|
+
kind: z.string(),
|
|
53
|
+
fileName: z.string(),
|
|
54
|
+
base64: z.string(),
|
|
55
|
+
mimeType: z.string().optional(),
|
|
56
|
+
}),
|
|
57
|
+
execute: async (args: z.infer<typeof uploadTakeoffInput.inputSchemaZod>): Promise<string> => {
|
|
58
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
59
|
+
return JSON.stringify({
|
|
60
|
+
message:
|
|
61
|
+
"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.",
|
|
62
|
+
projectId: args.projectId,
|
|
63
|
+
kind: args.kind,
|
|
64
|
+
fileName: args.fileName,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const runTakeoffExtract = {
|
|
70
|
+
name: "estimator_run_takeoff_extract",
|
|
71
|
+
description: "Run takeoff extractors on all uploaded inputs (idempotent).",
|
|
72
|
+
inputSchemaZod: projectIdSchema,
|
|
73
|
+
execute: async (args: z.infer<typeof projectIdSchema>): Promise<string> => {
|
|
74
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
75
|
+
const data = await client.post(`/estimator/takeoffs/${args.projectId}/extract`, {});
|
|
76
|
+
return JSON.stringify(data);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const listTakeoffRows = {
|
|
81
|
+
name: "estimator_list_takeoff_rows",
|
|
82
|
+
description: "List takeoff rows with evidence and review state.",
|
|
83
|
+
inputSchemaZod: z.object({
|
|
84
|
+
projectId: z.string().uuid(),
|
|
85
|
+
reviewState: z.string().optional(),
|
|
86
|
+
}),
|
|
87
|
+
execute: async (args: z.infer<typeof listTakeoffRows.inputSchemaZod>): Promise<string> => {
|
|
88
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
89
|
+
const qs = args.reviewState ? `?reviewState=${args.reviewState}` : "";
|
|
90
|
+
const data = await client.get(`/estimator/takeoffs/${args.projectId}/rows${qs}`);
|
|
91
|
+
return JSON.stringify(data);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const reviewTakeoffRow = {
|
|
96
|
+
name: "estimator_review_takeoff_row",
|
|
97
|
+
description: "Approve, skip, or edit a single takeoff row.",
|
|
98
|
+
inputSchemaZod: z.object({
|
|
99
|
+
projectId: z.string().uuid(),
|
|
100
|
+
rowId: z.string().uuid(),
|
|
101
|
+
action: z.enum(["approve", "skip", "edit"]),
|
|
102
|
+
edits: z
|
|
103
|
+
.object({
|
|
104
|
+
description: z.string().optional(),
|
|
105
|
+
quantity: z.number().optional(),
|
|
106
|
+
unit: z.string().optional(),
|
|
107
|
+
serviceCode: z.string().nullable().optional(),
|
|
108
|
+
})
|
|
109
|
+
.optional(),
|
|
110
|
+
}),
|
|
111
|
+
execute: async (args: z.infer<typeof reviewTakeoffRow.inputSchemaZod>): Promise<string> => {
|
|
112
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
113
|
+
const { projectId, rowId, ...body } = args;
|
|
114
|
+
const data = await client.patch(`/estimator/takeoffs/${projectId}/rows/${rowId}`, body);
|
|
115
|
+
return JSON.stringify(data);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const commitTakeoff = {
|
|
120
|
+
name: "estimator_commit_takeoff",
|
|
121
|
+
description: "Mark takeoff ready after all rows reviewed and area measurement approved (or override note).",
|
|
122
|
+
inputSchemaZod: z.object({
|
|
123
|
+
projectId: z.string().uuid(),
|
|
124
|
+
overrideNote: z.string().optional(),
|
|
125
|
+
}),
|
|
126
|
+
execute: async (args: z.infer<typeof commitTakeoff.inputSchemaZod>): Promise<string> => {
|
|
127
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
128
|
+
const data = await client.post(`/estimator/takeoffs/${args.projectId}/commit`, {
|
|
129
|
+
overrideNote: args.overrideNote,
|
|
130
|
+
});
|
|
131
|
+
return JSON.stringify(data);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const listSystemPacks = {
|
|
136
|
+
name: "estimator_list_system_packs",
|
|
137
|
+
description: "List built-in commercial and residential system packs.",
|
|
138
|
+
inputSchemaZod: z.object({ segment: z.enum(["commercial", "residential"]).optional() }),
|
|
139
|
+
execute: async (args: z.infer<typeof listSystemPacks.inputSchemaZod>): Promise<string> => {
|
|
140
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
141
|
+
const qs = args.segment ? `?segment=${args.segment}` : "";
|
|
142
|
+
const data = await client.get(`/estimator/system-packs${qs}`);
|
|
143
|
+
return JSON.stringify(data);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const applySystemPack = {
|
|
148
|
+
name: "estimator_apply_system_pack",
|
|
149
|
+
description: "Set system pack slug and optional service profile on takeoff project.",
|
|
150
|
+
inputSchemaZod: z.object({
|
|
151
|
+
projectId: z.string().uuid(),
|
|
152
|
+
systemPackSlug: z.string(),
|
|
153
|
+
serviceProfileId: z.string().optional(),
|
|
154
|
+
}),
|
|
155
|
+
execute: async (args: z.infer<typeof applySystemPack.inputSchemaZod>): Promise<string> => {
|
|
156
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
157
|
+
const data = await client.patch(`/estimator/takeoffs/${args.projectId}`, {
|
|
158
|
+
systemPackSlug: args.systemPackSlug,
|
|
159
|
+
serviceProfileId: args.serviceProfileId ?? null,
|
|
160
|
+
});
|
|
161
|
+
return JSON.stringify(data);
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const listServiceProfiles = {
|
|
166
|
+
name: "estimator_list_service_profiles",
|
|
167
|
+
description: "List service department profiles (includes v6 defaults).",
|
|
168
|
+
inputSchemaZod: z.object({}),
|
|
169
|
+
execute: async (): Promise<string> => {
|
|
170
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
171
|
+
const data = await client.get("/estimator/service-profiles");
|
|
172
|
+
return JSON.stringify(data);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const upsertServiceProfile = {
|
|
177
|
+
name: "estimator_upsert_service_profile",
|
|
178
|
+
description: "Create or update a service department profile.",
|
|
179
|
+
inputSchemaZod: z.object({
|
|
180
|
+
slug: z.string(),
|
|
181
|
+
name: z.string(),
|
|
182
|
+
segment: z.enum([
|
|
183
|
+
"commercial_service",
|
|
184
|
+
"commercial_production",
|
|
185
|
+
"residential_service",
|
|
186
|
+
"residential_production",
|
|
187
|
+
]),
|
|
188
|
+
laborMultiplier: z.number().optional(),
|
|
189
|
+
defaultWastePercent: z.number().optional(),
|
|
190
|
+
}),
|
|
191
|
+
execute: async (args: z.infer<typeof upsertServiceProfile.inputSchemaZod>): Promise<string> => {
|
|
192
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
193
|
+
const data = await client.post("/estimator/service-profiles", args);
|
|
194
|
+
return JSON.stringify(data);
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export const generateEstimateFromTakeoff = {
|
|
199
|
+
name: "estimator_generate_estimate_from_takeoff",
|
|
200
|
+
description:
|
|
201
|
+
"Generate draft estimate from committed takeoff + system pack. Preflight fails if catalog codes missing.",
|
|
202
|
+
inputSchemaZod: projectIdSchema,
|
|
203
|
+
execute: async (args: z.infer<typeof projectIdSchema>): Promise<string> => {
|
|
204
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
205
|
+
const data = await client.post(`/estimator/takeoffs/${args.projectId}/generate-estimate`, {});
|
|
206
|
+
return JSON.stringify(data);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const requestEstimateApproval = {
|
|
211
|
+
name: "estimator_request_estimate_approval",
|
|
212
|
+
description: "Request owner approval before customer send.",
|
|
213
|
+
inputSchemaZod: z.object({ estimateId: z.string().uuid(), note: z.string().optional() }),
|
|
214
|
+
execute: async (args: z.infer<typeof requestEstimateApproval.inputSchemaZod>): Promise<string> => {
|
|
215
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
216
|
+
const data = await client.post(`/estimator/estimates/${args.estimateId}/approval`, {
|
|
217
|
+
note: args.note,
|
|
218
|
+
});
|
|
219
|
+
return JSON.stringify(data);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const approveEstimate = {
|
|
224
|
+
name: "estimator_approve_estimate",
|
|
225
|
+
description: "Approve or reject estimate (workspace owner).",
|
|
226
|
+
inputSchemaZod: z.object({
|
|
227
|
+
estimateId: z.string().uuid(),
|
|
228
|
+
approvalId: z.string().uuid(),
|
|
229
|
+
action: z.enum(["approve", "reject"]),
|
|
230
|
+
note: z.string().optional(),
|
|
231
|
+
}),
|
|
232
|
+
execute: async (args: z.infer<typeof approveEstimate.inputSchemaZod>): Promise<string> => {
|
|
233
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
234
|
+
const data = await client.post(`/estimator/estimates/${args.estimateId}/approval`, {
|
|
235
|
+
approvalId: args.approvalId,
|
|
236
|
+
action: args.action,
|
|
237
|
+
note: args.note,
|
|
238
|
+
});
|
|
239
|
+
return JSON.stringify(data);
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export const getEstimateApproval = {
|
|
244
|
+
name: "estimator_get_estimate_approval",
|
|
245
|
+
description: "Get latest approval status for an estimate.",
|
|
246
|
+
inputSchemaZod: z.object({ estimateId: z.string().uuid() }),
|
|
247
|
+
execute: async (args: z.infer<typeof getEstimateApproval.inputSchemaZod>): Promise<string> => {
|
|
248
|
+
if (!client.hasApiKey()) return client.noApiKeyMessage();
|
|
249
|
+
const data = await client.get(`/estimator/estimates/${args.estimateId}/approval`);
|
|
250
|
+
return JSON.stringify(data);
|
|
251
|
+
},
|
|
252
|
+
};
|