@kynver-app/mcp-estimator 0.1.1 → 0.2.1
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/index.ts +18 -13
- package/src/lib/package-version.ts +55 -0
- package/src/server.ts +123 -101
- package/src/tools/takeoff-tools.ts +252 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Kynver Estimator MCP server — stdio entry point.
|
|
4
|
-
* Run: node dist/index.js
|
|
5
|
-
* Env: KYNVER_API_URL (default https://kynver.com), KYNVER_API_KEY (required for all tools)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// @generated — synced from packages/kynver-mcp-sse-lib/src/package-version.ts via scripts/sync-mcp-sse-server.mjs
|
|
2
|
+
// Do not edit by hand; change the canonical file and re-run the sync script.
|
|
3
|
+
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
function resolvePackageRoot(moduleUrl: string): string {
|
|
9
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
10
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
11
|
+
if (existsSync(join(dir, "package.json"))) return dir;
|
|
12
|
+
const parent = dirname(dir);
|
|
13
|
+
if (parent === dir) break;
|
|
14
|
+
dir = parent;
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`package.json not found above ${dirname(fileURLToPath(moduleUrl))}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Read a field from the nearest `package.json` above this module. */
|
|
20
|
+
export function readOwnPackageJsonField(
|
|
21
|
+
moduleUrl: string,
|
|
22
|
+
field: "version" | "name",
|
|
23
|
+
): string {
|
|
24
|
+
const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
|
|
25
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as Record<string, unknown>;
|
|
26
|
+
const value = pkg[field];
|
|
27
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
28
|
+
throw new Error(`Missing package.json ${field} at ${pkgPath}`);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function readOwnPackageVersion(moduleUrl: string = import.meta.url): string {
|
|
34
|
+
return readOwnPackageJsonField(moduleUrl, "version");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function wantsCliVersion(argv: string[]): boolean {
|
|
38
|
+
return argv.some((arg) => arg === "--version" || arg === "-v");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function printCliVersionAndExit(version: string, binName?: string): never {
|
|
42
|
+
console.log(binName ? `${binName} ${version}` : version);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** When `--version` / `-v` is present, print package version and exit before side effects. */
|
|
47
|
+
export function handleCliVersionFlag(
|
|
48
|
+
argv: string[],
|
|
49
|
+
moduleUrl: string = import.meta.url,
|
|
50
|
+
binName?: string,
|
|
51
|
+
): boolean {
|
|
52
|
+
if (!wantsCliVersion(argv)) return false;
|
|
53
|
+
printCliVersionAndExit(readOwnPackageVersion(moduleUrl), binName);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -1,101 +1,123 @@
|
|
|
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
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
register(catalogTools.
|
|
44
|
-
register(catalogTools.
|
|
45
|
-
register(catalogTools.
|
|
46
|
-
register(catalogTools.
|
|
47
|
-
register(catalogTools.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
register(
|
|
51
|
-
register(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
register(estimateTools.
|
|
55
|
-
register(estimateTools.
|
|
56
|
-
register(estimateTools.
|
|
57
|
-
register(estimateTools.
|
|
58
|
-
register(estimateTools.
|
|
59
|
-
register(estimateTools.
|
|
60
|
-
register(estimateTools.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
register(
|
|
64
|
-
register(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
register(
|
|
70
|
-
register(
|
|
71
|
-
|
|
72
|
-
// ---
|
|
73
|
-
register(
|
|
74
|
-
register(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
register(
|
|
79
|
-
register(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
register(
|
|
84
|
-
register(
|
|
85
|
-
|
|
86
|
-
// ---
|
|
87
|
-
register(
|
|
88
|
-
register(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
register(companyTools.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
register(
|
|
95
|
-
register(
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
import { readOwnPackageVersion } from "./lib/package-version.js";
|
|
18
|
+
|
|
19
|
+
const PACKAGE_VERSION = readOwnPackageVersion(import.meta.url);
|
|
20
|
+
|
|
21
|
+
type ToolConfig = { description: string; inputSchema: unknown };
|
|
22
|
+
|
|
23
|
+
function toolResult(text: string) {
|
|
24
|
+
return { content: [{ type: "text" as const, text }] };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createEstimatorServer(): McpServer {
|
|
28
|
+
const server = new McpServer(
|
|
29
|
+
{ name: "kynver-mcp-estimator", version: PACKAGE_VERSION },
|
|
30
|
+
{ capabilities: { tools: {} } }
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
function register(
|
|
34
|
+
name: string,
|
|
35
|
+
config: ToolConfig,
|
|
36
|
+
run: (args: unknown) => Promise<string>
|
|
37
|
+
) {
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
server.registerTool(name, config as any, async (args: any) => toolResult(await run(args)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// --- Catalog tools (9) ---
|
|
43
|
+
register(catalogTools.importCatalog.name, { description: catalogTools.importCatalog.description, inputSchema: catalogTools.importCatalog.inputSchemaZod }, (args) => catalogTools.importCatalog.execute(args as any));
|
|
44
|
+
register(catalogTools.reviewImport.name, { description: catalogTools.reviewImport.description, inputSchema: catalogTools.reviewImport.inputSchemaZod }, (args) => catalogTools.reviewImport.execute(args as any));
|
|
45
|
+
register(catalogTools.commitImport.name, { description: catalogTools.commitImport.description, inputSchema: catalogTools.commitImport.inputSchemaZod }, (args) => catalogTools.commitImport.execute(args as any));
|
|
46
|
+
register(catalogTools.setupPrice.name, { description: catalogTools.setupPrice.description, inputSchema: catalogTools.setupPrice.inputSchemaZod }, (args) => catalogTools.setupPrice.execute(args as any));
|
|
47
|
+
register(catalogTools.updatePrice.name, { description: catalogTools.updatePrice.description, inputSchema: catalogTools.updatePrice.inputSchemaZod }, (args) => catalogTools.updatePrice.execute(args as any));
|
|
48
|
+
register(catalogTools.getCatalog.name, { description: catalogTools.getCatalog.description, inputSchema: catalogTools.getCatalog.inputSchemaZod }, (args) => catalogTools.getCatalog.execute(args as any));
|
|
49
|
+
register(catalogTools.getConfig.name, { description: catalogTools.getConfig.description, inputSchema: catalogTools.getConfig.inputSchemaZod }, (args) => catalogTools.getConfig.execute(args as any));
|
|
50
|
+
register(catalogTools.updateConfig.name, { description: catalogTools.updateConfig.description, inputSchema: catalogTools.updateConfig.inputSchemaZod }, (args) => catalogTools.updateConfig.execute(args as any));
|
|
51
|
+
register(catalogTools.importTemplate.name, { description: catalogTools.importTemplate.description, inputSchema: catalogTools.importTemplate.inputSchemaZod }, (args) => catalogTools.importTemplate.execute(args as any));
|
|
52
|
+
|
|
53
|
+
// --- Estimate tools (11) ---
|
|
54
|
+
register(estimateTools.createReplacement.name, { description: estimateTools.createReplacement.description, inputSchema: estimateTools.createReplacement.inputSchemaZod }, (args) => estimateTools.createReplacement.execute(args as any));
|
|
55
|
+
register(estimateTools.createRepair.name, { description: estimateTools.createRepair.description, inputSchema: estimateTools.createRepair.inputSchemaZod }, (args) => estimateTools.createRepair.execute(args as any));
|
|
56
|
+
register(estimateTools.getEstimate.name, { description: estimateTools.getEstimate.description, inputSchema: estimateTools.getEstimate.inputSchemaZod }, (args) => estimateTools.getEstimate.execute(args as any));
|
|
57
|
+
register(estimateTools.listEstimates.name, { description: estimateTools.listEstimates.description, inputSchema: estimateTools.listEstimates.inputSchemaZod }, (args) => estimateTools.listEstimates.execute(args as any));
|
|
58
|
+
register(estimateTools.addLineItem.name, { description: estimateTools.addLineItem.description, inputSchema: estimateTools.addLineItem.inputSchemaZod }, (args) => estimateTools.addLineItem.execute(args as any));
|
|
59
|
+
register(estimateTools.removeLineItem.name, { description: estimateTools.removeLineItem.description, inputSchema: estimateTools.removeLineItem.inputSchemaZod }, (args) => estimateTools.removeLineItem.execute(args as any));
|
|
60
|
+
register(estimateTools.updateLineItem.name, { description: estimateTools.updateLineItem.description, inputSchema: estimateTools.updateLineItem.inputSchemaZod }, (args) => estimateTools.updateLineItem.execute(args as any));
|
|
61
|
+
register(estimateTools.adjustToTarget.name, { description: estimateTools.adjustToTarget.description, inputSchema: estimateTools.adjustToTarget.inputSchemaZod }, (args) => estimateTools.adjustToTarget.execute(args as any));
|
|
62
|
+
register(estimateTools.updateEstimate.name, { description: estimateTools.updateEstimate.description, inputSchema: estimateTools.updateEstimate.inputSchemaZod }, (args) => estimateTools.updateEstimate.execute(args as any));
|
|
63
|
+
register(estimateTools.deleteEstimate.name, { description: estimateTools.deleteEstimate.description, inputSchema: estimateTools.deleteEstimate.inputSchemaZod }, (args) => estimateTools.deleteEstimate.execute(args as any));
|
|
64
|
+
register(estimateTools.duplicateEstimate.name, { description: estimateTools.duplicateEstimate.description, inputSchema: estimateTools.duplicateEstimate.inputSchemaZod }, (args) => estimateTools.duplicateEstimate.execute(args as any));
|
|
65
|
+
|
|
66
|
+
// --- Template tools (4) ---
|
|
67
|
+
register(templateTools.saveTemplate.name, { description: templateTools.saveTemplate.description, inputSchema: templateTools.saveTemplate.inputSchemaZod }, (args) => templateTools.saveTemplate.execute(args as any));
|
|
68
|
+
register(templateTools.listTemplates.name, { description: templateTools.listTemplates.description, inputSchema: templateTools.listTemplates.inputSchemaZod }, (args) => templateTools.listTemplates.execute(args as any));
|
|
69
|
+
register(templateTools.deleteTemplate.name, { description: templateTools.deleteTemplate.description, inputSchema: templateTools.deleteTemplate.inputSchemaZod }, (args) => templateTools.deleteTemplate.execute(args as any));
|
|
70
|
+
register(templateTools.createFromTemplate.name, { description: templateTools.createFromTemplate.description, inputSchema: templateTools.createFromTemplate.inputSchemaZod }, (args) => templateTools.createFromTemplate.execute(args as any));
|
|
71
|
+
|
|
72
|
+
// --- Version tools (2) ---
|
|
73
|
+
register(versionTools.listVersions.name, { description: versionTools.listVersions.description, inputSchema: versionTools.listVersions.inputSchemaZod }, (args) => versionTools.listVersions.execute(args as any));
|
|
74
|
+
register(versionTools.getVersion.name, { description: versionTools.getVersion.description, inputSchema: versionTools.getVersion.inputSchemaZod }, (args) => versionTools.getVersion.execute(args as any));
|
|
75
|
+
|
|
76
|
+
// --- Photo tools (3) ---
|
|
77
|
+
register(photoTools.uploadPhoto.name, { description: photoTools.uploadPhoto.description, inputSchema: photoTools.uploadPhoto.inputSchemaZod }, (args) => photoTools.uploadPhoto.execute(args as any));
|
|
78
|
+
register(photoTools.listPhotos.name, { description: photoTools.listPhotos.description, inputSchema: photoTools.listPhotos.inputSchemaZod }, (args) => photoTools.listPhotos.execute(args as any));
|
|
79
|
+
register(photoTools.deletePhoto.name, { description: photoTools.deletePhoto.description, inputSchema: photoTools.deletePhoto.inputSchemaZod }, (args) => photoTools.deletePhoto.execute(args as any));
|
|
80
|
+
|
|
81
|
+
// --- Stats / PDF tools (3) ---
|
|
82
|
+
register(statsPdfTools.getStats.name, { description: statsPdfTools.getStats.description, inputSchema: statsPdfTools.getStats.inputSchemaZod }, (args) => statsPdfTools.getStats.execute(args as any));
|
|
83
|
+
register(statsPdfTools.exportPdf.name, { description: statsPdfTools.exportPdf.description, inputSchema: statsPdfTools.exportPdf.inputSchemaZod }, (args) => statsPdfTools.exportPdf.execute(args as any));
|
|
84
|
+
register(statsPdfTools.updateOutputConfig.name, { description: statsPdfTools.updateOutputConfig.description, inputSchema: statsPdfTools.updateOutputConfig.inputSchemaZod }, (args) => statsPdfTools.updateOutputConfig.execute(args as any));
|
|
85
|
+
|
|
86
|
+
// --- Vision tools (2) ---
|
|
87
|
+
register(visionTools.analyzePhotos.name, { description: visionTools.analyzePhotos.description, inputSchema: visionTools.analyzePhotos.inputSchemaZod }, (args) => visionTools.analyzePhotos.execute(args as any));
|
|
88
|
+
register(visionTools.analyzeDocument.name, { description: visionTools.analyzeDocument.description, inputSchema: visionTools.analyzeDocument.inputSchemaZod }, (args) => visionTools.analyzeDocument.execute(args as any));
|
|
89
|
+
|
|
90
|
+
// --- Company tools (5) ---
|
|
91
|
+
register(companyTools.createCompany.name, { description: companyTools.createCompany.description, inputSchema: companyTools.createCompany.inputSchemaZod }, (args) => companyTools.createCompany.execute(args as any));
|
|
92
|
+
register(companyTools.listCompanies.name, { description: companyTools.listCompanies.description, inputSchema: companyTools.listCompanies.inputSchemaZod }, (args) => companyTools.listCompanies.execute(args as any));
|
|
93
|
+
register(companyTools.getCompany.name, { description: companyTools.getCompany.description, inputSchema: companyTools.getCompany.inputSchemaZod }, (args) => companyTools.getCompany.execute(args as any));
|
|
94
|
+
register(companyTools.updateCompany.name, { description: companyTools.updateCompany.description, inputSchema: companyTools.updateCompany.inputSchemaZod }, (args) => companyTools.updateCompany.execute(args as any));
|
|
95
|
+
register(companyTools.deleteCompany.name, { description: companyTools.deleteCompany.description, inputSchema: companyTools.deleteCompany.inputSchemaZod }, (args) => companyTools.deleteCompany.execute(args as any));
|
|
96
|
+
|
|
97
|
+
// --- Client tools (5) ---
|
|
98
|
+
register(clientTools.createClient.name, { description: clientTools.createClient.description, inputSchema: clientTools.createClient.inputSchemaZod }, (args) => clientTools.createClient.execute(args as any));
|
|
99
|
+
register(clientTools.listClients.name, { description: clientTools.listClients.description, inputSchema: clientTools.listClients.inputSchemaZod }, (args) => clientTools.listClients.execute(args as any));
|
|
100
|
+
register(clientTools.getClient.name, { description: clientTools.getClient.description, inputSchema: clientTools.getClient.inputSchemaZod }, (args) => clientTools.getClient.execute(args as any));
|
|
101
|
+
register(clientTools.updateClient.name, { description: clientTools.updateClient.description, inputSchema: clientTools.updateClient.inputSchemaZod }, (args) => clientTools.updateClient.execute(args as any));
|
|
102
|
+
register(clientTools.deleteClient.name, { description: clientTools.deleteClient.description, inputSchema: clientTools.deleteClient.inputSchemaZod }, (args) => clientTools.deleteClient.execute(args as any));
|
|
103
|
+
|
|
104
|
+
// --- Takeoff / approval tools (16) ---
|
|
105
|
+
register(takeoffTools.createTakeoffProject.name, { description: takeoffTools.createTakeoffProject.description, inputSchema: takeoffTools.createTakeoffProject.inputSchemaZod }, (args) => takeoffTools.createTakeoffProject.execute(args as any));
|
|
106
|
+
register(takeoffTools.getTakeoffProject.name, { description: takeoffTools.getTakeoffProject.description, inputSchema: takeoffTools.getTakeoffProject.inputSchemaZod }, (args) => takeoffTools.getTakeoffProject.execute(args as any));
|
|
107
|
+
register(takeoffTools.listTakeoffProjects.name, { description: takeoffTools.listTakeoffProjects.description, inputSchema: takeoffTools.listTakeoffProjects.inputSchemaZod }, (args) => takeoffTools.listTakeoffProjects.execute(args as any));
|
|
108
|
+
register(takeoffTools.uploadTakeoffInput.name, { description: takeoffTools.uploadTakeoffInput.description, inputSchema: takeoffTools.uploadTakeoffInput.inputSchemaZod }, (args) => takeoffTools.uploadTakeoffInput.execute(args as any));
|
|
109
|
+
register(takeoffTools.runTakeoffExtract.name, { description: takeoffTools.runTakeoffExtract.description, inputSchema: takeoffTools.runTakeoffExtract.inputSchemaZod }, (args) => takeoffTools.runTakeoffExtract.execute(args as any));
|
|
110
|
+
register(takeoffTools.listTakeoffRows.name, { description: takeoffTools.listTakeoffRows.description, inputSchema: takeoffTools.listTakeoffRows.inputSchemaZod }, (args) => takeoffTools.listTakeoffRows.execute(args as any));
|
|
111
|
+
register(takeoffTools.reviewTakeoffRow.name, { description: takeoffTools.reviewTakeoffRow.description, inputSchema: takeoffTools.reviewTakeoffRow.inputSchemaZod }, (args) => takeoffTools.reviewTakeoffRow.execute(args as any));
|
|
112
|
+
register(takeoffTools.commitTakeoff.name, { description: takeoffTools.commitTakeoff.description, inputSchema: takeoffTools.commitTakeoff.inputSchemaZod }, (args) => takeoffTools.commitTakeoff.execute(args as any));
|
|
113
|
+
register(takeoffTools.listSystemPacks.name, { description: takeoffTools.listSystemPacks.description, inputSchema: takeoffTools.listSystemPacks.inputSchemaZod }, (args) => takeoffTools.listSystemPacks.execute(args as any));
|
|
114
|
+
register(takeoffTools.applySystemPack.name, { description: takeoffTools.applySystemPack.description, inputSchema: takeoffTools.applySystemPack.inputSchemaZod }, (args) => takeoffTools.applySystemPack.execute(args as any));
|
|
115
|
+
register(takeoffTools.listServiceProfiles.name, { description: takeoffTools.listServiceProfiles.description, inputSchema: takeoffTools.listServiceProfiles.inputSchemaZod }, (args) => takeoffTools.listServiceProfiles.execute(args as any));
|
|
116
|
+
register(takeoffTools.upsertServiceProfile.name, { description: takeoffTools.upsertServiceProfile.description, inputSchema: takeoffTools.upsertServiceProfile.inputSchemaZod }, (args) => takeoffTools.upsertServiceProfile.execute(args as any));
|
|
117
|
+
register(takeoffTools.generateEstimateFromTakeoff.name, { description: takeoffTools.generateEstimateFromTakeoff.description, inputSchema: takeoffTools.generateEstimateFromTakeoff.inputSchemaZod }, (args) => takeoffTools.generateEstimateFromTakeoff.execute(args as any));
|
|
118
|
+
register(takeoffTools.requestEstimateApproval.name, { description: takeoffTools.requestEstimateApproval.description, inputSchema: takeoffTools.requestEstimateApproval.inputSchemaZod }, (args) => takeoffTools.requestEstimateApproval.execute(args as any));
|
|
119
|
+
register(takeoffTools.approveEstimate.name, { description: takeoffTools.approveEstimate.description, inputSchema: takeoffTools.approveEstimate.inputSchemaZod }, (args) => takeoffTools.approveEstimate.execute(args as any));
|
|
120
|
+
register(takeoffTools.getEstimateApproval.name, { description: takeoffTools.getEstimateApproval.description, inputSchema: takeoffTools.getEstimateApproval.inputSchemaZod }, (args) => takeoffTools.getEstimateApproval.execute(args as any));
|
|
121
|
+
|
|
122
|
+
return server;
|
|
123
|
+
}
|
|
@@ -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
|
+
};
|